Skip to content

Commit be9a730

Browse files
committed
feat: Extends NUsersList component with function filters
Adds support for function-based filters to the `NUsersList` component, enabling more complex filtering logic and integration with connected data. This enhancement provides greater flexibility in filtering users based on custom criteria and relationships.
1 parent 8136322 commit be9a730

File tree

3 files changed

+248
-15
lines changed

3 files changed

+248
-15
lines changed

docs/user-guide/components.md

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -585,19 +585,23 @@ const handleDelete = (user) => {
585585
|------|------|---------|-------------|
586586
| `displayFields` | `string[]` | `['id', 'name', 'email', 'role', 'created_at']` | Fields to display for each user |
587587
| `fieldLabels` | `Record<string, string>` | Default labels | Custom labels for fields |
588-
| `filter` | `Partial<User>` | `{}` | Filter criteria to apply to the user list |
588+
| `filter` | `Partial<User> \| ((object: unknown) => boolean)` | `{}` | Filter criteria or function to apply to the user list |
589589

590590
#### Filtering Users
591591

592-
The `NUsersList` component supports real-time filtering of users based on any user field. The filter prop accepts a `Partial<User>` object where you can specify criteria for any user property.
592+
The `NUsersList` component supports two types of filtering:
593+
594+
1. **Object-based filtering** - Filter by user properties using a `Partial<User>` object
595+
2. **Function-based filtering** - Use custom filter functions for complex logic and connected data
593596

594597
**Filter Behavior:**
595598
- **String fields** (name, email, role): Case-insensitive partial matching
596599
- **Other fields** (id, active, etc.): Exact matching
597600
- **Empty values**: Ignored (no filtering applied for that field)
598601
- **Multiple criteria**: All conditions must be met (AND logic)
602+
- **Function filters**: Return `true` to show the user, `false` to hide it
599603

600-
**Basic Filter Examples:**
604+
**Object-based Filter Examples:**
601605

602606
```vue
603607
<script setup>
@@ -634,6 +638,125 @@ const multiFilter = ref({
634638
</template>
635639
```
636640

641+
**Function-based Filter Examples:**
642+
643+
Function-based filtering is perfect for complex logic and when working with connected data from joined tables:
644+
645+
```vue
646+
<script setup>
647+
import { ref } from 'vue'
648+
649+
// Simple filter: users with credits > 100
650+
const creditsFilter = (obj) => {
651+
return obj.credits > 100
652+
}
653+
654+
// Complex filter: users with high credits and admin role
655+
const premiumAdminFilter = (obj) => {
656+
return obj.credits > 100 && obj.role === 'admin'
657+
}
658+
659+
// Filter by connected data (e.g., from joined tables)
660+
const departmentFilter = (obj) => {
661+
return obj.department?.name === 'Engineering'
662+
}
663+
664+
// Filter by nested properties
665+
const activePremiumFilter = (obj) => {
666+
return obj.profile?.isActive && obj.credits > 50
667+
}
668+
669+
// Dynamic filter with reactive state
670+
const showHighCredits = ref(false)
671+
const dynamicFilter = (obj) => {
672+
if (!showHighCredits.value) return true
673+
return obj.credits > 100
674+
}
675+
</script>
676+
677+
<template>
678+
<!-- Simple credits filter -->
679+
<NUsersList :filter="creditsFilter" />
680+
681+
<!-- Complex filter with multiple conditions -->
682+
<NUsersList :filter="premiumAdminFilter" />
683+
684+
<!-- Filter by connected data -->
685+
<NUsersList :filter="departmentFilter" />
686+
687+
<!-- Dynamic filter with toggle -->
688+
<div>
689+
<button @click="showHighCredits = !showHighCredits">
690+
{{ showHighCredits ? 'Show All Users' : 'Show High Credits Only' }}
691+
</button>
692+
<NUsersList :filter="dynamicFilter" />
693+
</div>
694+
</template>
695+
```
696+
697+
**Real-world Example with Connected Data:**
698+
699+
```vue
700+
<script setup>
701+
import { ref } from 'vue'
702+
703+
// Example: Users with data from joined tables
704+
// User object might contain: { id, name, email, credits, department: { name, budget }, permissions: [...] }
705+
706+
const showOnlyEngineers = ref(false)
707+
const showOnlyHighCredits = ref(false)
708+
709+
const engineerFilter = (obj) => {
710+
return obj.department?.name === 'Engineering'
711+
}
712+
713+
const highCreditsFilter = (obj) => {
714+
return obj.credits > 100
715+
}
716+
717+
const combinedFilter = (obj) => {
718+
let shouldShow = true
719+
720+
if (showOnlyEngineers.value) {
721+
shouldShow = shouldShow && obj.department?.name === 'Engineering'
722+
}
723+
724+
if (showOnlyHighCredits.value) {
725+
shouldShow = shouldShow && obj.credits > 100
726+
}
727+
728+
return shouldShow
729+
}
730+
</script>
731+
732+
<template>
733+
<div>
734+
<div class="filter-controls">
735+
<label>
736+
<input v-model="showOnlyEngineers" type="checkbox">
737+
Show only Engineers
738+
</label>
739+
<label>
740+
<input v-model="showOnlyHighCredits" type="checkbox">
741+
Show only High Credits (>100)
742+
</label>
743+
</div>
744+
745+
<NUsersList :filter="combinedFilter">
746+
<template #user="{ user }">
747+
<div class="user-card">
748+
<h3>{{ user.name }}</h3>
749+
<p>{{ user.email }}</p>
750+
<p>Credits: {{ user.credits }}</p>
751+
<p v-if="user.department">Department: {{ user.department.name }}</p>
752+
<p v-if="user.permissions">Permissions: {{ user.permissions.join(', ') }}</p>
753+
</div>
754+
</template>
755+
</NUsersList>
756+
</div>
757+
</template>
758+
```
759+
637760
**Dynamic Filtering with User Input:**
638761

639762
```vue

playground/pages/users-custom.vue

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,117 @@
11
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
4+
// Example: Filter users based on connected data
5+
const showOnlyAdmins = ref(false)
6+
7+
const adminFilter = (obj: unknown) => {
8+
const item = obj as any
9+
return item.role === 'admin' || item.permissions?.includes('admin')
10+
}
11+
12+
// Combined filter function
13+
const userFilter = (obj: unknown) => {
14+
if (!showOnlyAdmins.value ) {
15+
return true // Show all users
16+
}
17+
18+
let shouldShow = true
19+
20+
if (showOnlyAdmins.value) {
21+
shouldShow = shouldShow && adminFilter(obj)
22+
}
23+
24+
25+
return shouldShow
26+
}
27+
28+
// Object-based filter example (for simple property matching)
29+
const objectFilter = ref<{ role?: string }>({})
30+
31+
const toggleObjectFilter = () => {
32+
objectFilter.value = objectFilter.value.role ? {} : { role: 'admin' }
33+
}
234
</script>
335

436
<template>
5-
<NUsersList>
6-
<template #user="{ user }">
7-
<div>
8-
<h3>{{ user.name }}</h3>
9-
<p>{{ user.email }}</p>
10-
<p>{{ user.role }}</p>
11-
</div>
12-
</template>
13-
</NUsersList>
37+
<div>
38+
<div class="filter-controls">
39+
<button @click="showOnlyAdmins = !showOnlyAdmins">
40+
{{ showOnlyAdmins ? 'Show All Users' : 'Show Only Admins' }}
41+
</button>
42+
43+
<button @click="toggleObjectFilter">
44+
{{ objectFilter.role ? 'Clear Object Filter' : 'Filter by Admin Role' }}
45+
</button>
46+
</div>
47+
48+
<!-- Using function-based filter -->
49+
<h3>Function-based Filter (for complex logic):</h3>
50+
<NUsersList :filter="userFilter">
51+
<template #user="{ user }">
52+
<div class="user-card">
53+
<h3>{{ user.name }}</h3>
54+
<p>{{ user.email }}</p>
55+
<p>Role: {{ user.role }}</p>
56+
</div>
57+
</template>
58+
</NUsersList>
59+
60+
<!-- Using object-based filter -->
61+
<h3>Object-based Filter (for simple property matching):</h3>
62+
<NUsersList :filter="objectFilter">
63+
<template #user="{ user }">
64+
<div class="user-card">
65+
<h3>{{ user.name }}</h3>
66+
<p>{{ user.email }}</p>
67+
<p>Role: {{ user.role }}</p>
68+
</div>
69+
</template>
70+
</NUsersList>
71+
</div>
1472
</template>
1573

1674
<style scoped>
75+
.filter-controls {
76+
margin-bottom: 2rem;
77+
display: flex;
78+
gap: 1rem;
79+
}
80+
81+
.filter-controls button {
82+
padding: 0.5rem 1rem;
83+
background-color: #007bff;
84+
color: white;
85+
border: none;
86+
border-radius: 4px;
87+
cursor: pointer;
88+
}
89+
90+
.filter-controls button:hover {
91+
background-color: #0056b3;
92+
}
93+
94+
.user-card {
95+
border: 1px solid #ddd;
96+
border-radius: 8px;
97+
padding: 1rem;
98+
margin-bottom: 1rem;
99+
background-color: #f9f9f9;
100+
}
101+
102+
.user-card h3 {
103+
margin: 0 0 0.5rem 0;
104+
color: #333;
105+
}
106+
107+
.user-card p {
108+
margin: 0.25rem 0;
109+
color: #666;
110+
}
111+
112+
h3 {
113+
margin-top: 2rem;
114+
margin-bottom: 1rem;
115+
color: #333;
116+
}
17117
</style>

src/runtime/components/NUsersList.vue

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { defaultDisplayFields, defaultFieldLabels, type User } from 'nuxt-users/
88
interface Props {
99
displayFields?: string[]
1010
fieldLabels?: Record<string, string>
11-
filter?: Partial<User>
11+
filter?: Partial<User> | ((object: unknown) => boolean)
1212
}
1313
1414
const props = withDefaults(defineProps<Props>(), {
@@ -32,12 +32,22 @@ const error = computed(() => usersComposable.value?.error ?? null)
3232
3333
// Filter users based on the filter prop
3434
const users = computed(() => {
35-
if (!props.filter || Object.keys(props.filter).length === 0) {
35+
if (!props.filter) {
36+
return allUsers.value
37+
}
38+
39+
// Handle function-based filtering
40+
if (typeof props.filter === 'function') {
41+
return allUsers.value.filter(props.filter)
42+
}
43+
44+
// Handle object-based filtering (existing logic)
45+
if (Object.keys(props.filter).length === 0) {
3646
return allUsers.value
3747
}
3848
3949
return allUsers.value.filter((user) => {
40-
return Object.entries(props.filter).every(([key, value]) => {
50+
return Object.entries(props.filter as Partial<User>).every(([key, value]) => {
4151
if (value === undefined || value === null || value === '') {
4252
return true
4353
}

0 commit comments

Comments
 (0)