Skip to content

Commit f5e9f35

Browse files
authored
Merge pull request #97 from BEXIS2/table
Update Facets with new params and add onChange action
2 parents c9d8524 + 7c514ca commit f5e9f35

File tree

4 files changed

+240
-146
lines changed

4 files changed

+240
-146
lines changed

src/lib/components/Facets/Facets.svelte

Lines changed: 120 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,192 @@
11
<script lang="ts">
2+
import { createEventDispatcher } from 'svelte';
23
import { getModalStore, Modal, TreeView, TreeViewItem } from '@skeletonlabs/skeleton';
34
45
import ShowMore from './ShowMore.svelte';
5-
import type { FacetOption, FacetGroup } from '$models/Models';
6+
import type { FacetGroup, SelectedFacetGroup } from '$models/Models';
67
78
export let groupSelection = false;
8-
export let groups: FacetGroup;
9-
export let selected: FacetGroup;
10-
export let selectedGroups: { [key: string]: boolean } = {};
9+
export let groups: FacetGroup[];
1110
export let showAll = false;
1211
export let open = false;
1312
13+
let displayedGroups = structuredClone(groups);
14+
15+
let selected: { [key: string]: SelectedFacetGroup } = groups.reduce((acc, g) => {
16+
const children = g.children.reduce((acc, c) => {
17+
acc[c.name] = {
18+
...c,
19+
selected: false
20+
};
21+
return acc;
22+
}, {});
23+
24+
acc[g.name] = {
25+
...g,
26+
children,
27+
selected: false
28+
};
29+
30+
return acc;
31+
}, {});
32+
let selectedItems: {
33+
[key: string]: {
34+
[key: string]: boolean;
35+
};
36+
} = {};
37+
let selectedGroups: { [key: string]: boolean } = {};
38+
39+
Object.keys(selected).forEach((groupName) => {
40+
selectedItems[groupName] = {};
41+
Object.keys(selected[groupName].children).forEach((itemName) => {
42+
selectedItems[groupName][itemName] = false;
43+
});
44+
selectedGroups[groupName] = false;
45+
});
46+
47+
const dispatch = createEventDispatcher();
48+
1449
const modalStore = getModalStore();
15-
const showMore = (group: string) => {
50+
const showMore = (group: SelectedFacetGroup) => {
1651
modalStore.trigger({
1752
type: 'component',
18-
title: `${group}`,
53+
title: `${group.displayName}`,
1954
component: {
2055
ref: ShowMore,
2156
props: {
2257
group,
2358
handleSave,
24-
handleCancel,
25-
selected: selected[group],
26-
items: groups[group].sort((a, b) => a.value.localeCompare(b.value))
59+
handleCancel
2760
}
2861
}
2962
});
3063
};
3164
32-
const handleSave = (group: string, selectedItems: FacetOption[]) => {
33-
selected[group] = selectedItems;
65+
const handleSave = (group: SelectedFacetGroup) => {
66+
Object.keys(group.children).forEach((key) => {
67+
selectedItems[group.name][key] = group.children[key].selected;
68+
});
3469
modalStore.close();
3570
};
3671
3772
const handleCancel = () => {
3873
modalStore.close();
3974
};
4075
76+
const mapSelected = (type: 'items' | 'groups') => {
77+
const changed: any = [];
78+
79+
if (type === 'items') {
80+
Object.keys(selectedItems).forEach((group) => {
81+
Object.keys(selectedItems[group]).forEach((item) => {
82+
if (selectedItems[group][item] !== selected[group].children[item].selected) {
83+
changed.push({
84+
parent: group,
85+
selectedItem: item
86+
});
87+
selected[group].children[item].selected = selectedItems[group][item];
88+
}
89+
});
90+
});
91+
} else {
92+
Object.keys(selectedGroups).forEach((group) => {
93+
if (selectedGroups[group] !== selected[group].selected) {
94+
changed.push({
95+
parent: null,
96+
selectedItem: group
97+
});
98+
selected[group].selected = selectedGroups[group];
99+
}
100+
});
101+
}
102+
103+
dispatch('change', changed);
104+
};
105+
41106
const sortOptions = () => {
42107
// Sort facets in a descending order if count exits, or sort alphabetically
43-
Object.keys(groups).forEach((group) => {
44-
groups[group] = [
45-
...selected[group].sort((a, b) => {
108+
Object.keys(selected).forEach((group) => {
109+
const checked = Object.keys(selected[group].children)
110+
.filter((item) => selected[group].children[item].selected)
111+
.map((item) => selected[group].children[item])
112+
.sort((a, b) => {
46113
if (a.count != undefined && b.count != undefined) {
47114
return b.count - a.count;
48115
}
49-
return a.value.localeCompare(b.value);
50-
}),
51-
...groups[group]
52-
.filter((item) => !selected[group].includes(item))
53-
.sort((a, b) => {
54-
if (a.count != undefined && b.count != undefined) {
55-
return b.count - a.count;
56-
}
57-
return a.value.localeCompare(b.value);
58-
})
116+
return a.displayName.localeCompare(b.displayName);
117+
})
118+
.map((item) => item.name);
119+
120+
const unchecked = Object.keys(selected[group].children).filter(
121+
(item) => !checked.includes(item)
122+
);
123+
124+
const groupIndex = displayedGroups.findIndex((g) => g.name === group);
125+
126+
displayedGroups[groupIndex].children = [
127+
...checked.map(
128+
(item) => displayedGroups[groupIndex].children.find((i) => i.name === item)!
129+
),
130+
...unchecked.map(
131+
(item) => displayedGroups[groupIndex].children.find((i) => i.name === item)!
132+
)
59133
];
60134
});
61135
};
62136
63-
$: selected, sortOptions();
137+
$: selectedItems, mapSelected('items'), sortOptions();
138+
$: selectedGroups, mapSelected('groups');
64139
</script>
65140

66141
<TreeView selection={groupSelection} multiple={groupSelection} padding="p-1" hover="">
67-
{#each Object.keys(groups) as group}
142+
{#each displayedGroups as group}
68143
<TreeViewItem
69-
name="groups"
70-
value={group}
144+
name={group.name}
145+
value={group.name}
71146
{open}
72147
hyphenOpacity="opacity-0"
148+
bind:checked={selectedGroups[group.name]}
73149
bind:group={selectedGroups}
74-
bind:checked={selectedGroups[group]}
75150
>
76-
<p class="font-semibold">{group}</p>
151+
<p class="font-semibold">{group.displayName}</p>
77152

78153
<svelte:fragment slot="children">
79154
<!-- If more than 5 choices, show the remaining in the Modal -->
80155
{#if !showAll}
81-
{#each groups[group].slice(0, 5) as item}
156+
{#each group.children.slice(0, 5) as item}
82157
<TreeViewItem
83-
bind:group={selected[group]}
84-
name={group}
85-
value={item}
158+
bind:group={selectedItems[group.name]}
159+
name={item.name}
160+
value={item.displayName}
86161
hyphenOpacity="opacity-0"
162+
bind:checked={selectedItems[group.name][item.name]}
87163
spacing="space-x-3"
88164
selection
89165
multiple
90166
>
91-
<p>{item.value} ({item.count})</p>
167+
<p>{item.displayName} ({item.count})</p>
92168
</TreeViewItem>
93169
{/each}
94170
<!-- Trigger for the Modal to view all options -->
95-
{#if groups[group].length > 5}
171+
{#if group.children.length > 5}
96172
<TreeViewItem hyphenOpacity="opacity-0">
97-
<button class="anchor" on:click={() => showMore(group)}>more</button></TreeViewItem
173+
<button class="anchor" on:click={() => showMore(selected[group.name])}>more</button
174+
></TreeViewItem
98175
>
99176
{/if}
100177
{:else}
101-
{#each groups[group] as item}
178+
{#each group.children as item}
102179
<TreeViewItem
103-
bind:group={selected[group]}
104-
name={group}
105-
value={item}
180+
bind:group={selectedItems[group.name]}
181+
bind:checked={selectedItems[group.name][item.name]}
182+
name={item.name}
183+
value={item.displayName}
106184
hyphenOpacity="opacity-0"
107185
spacing="space-x-3"
108186
selection
109187
multiple
110188
>
111-
<p>{item.value} ({item.count})</p>
189+
<p>{item.displayName} ({item.count})</p>
112190
</TreeViewItem>
113191
{/each}
114192
{/if}

src/lib/components/Facets/ShowMore.svelte

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,34 @@
11
<script lang="ts">
2-
import type { FacetOption } from '$models/Models';
2+
import type { SelectedFacetGroup } from '$models/Models';
33
4-
export let group: string; // Group name
5-
export let items: FacetOption[]; // All possible choices
6-
export let selected: FacetOption[]; // Initially selected items
7-
export let handleSave: (group: string, selectedItems: FacetOption[]) => {};
4+
export let group: SelectedFacetGroup;
5+
export let handleSave: (group: SelectedFacetGroup) => {};
86
export let handleCancel: () => {};
97
10-
// This local variable is needed for resetting the Modal when the user cancels selection.
11-
let selectedItems = selected; // Selected items in the Modal.
12-
13-
const handleCheck = (e, index: number) => {
14-
const target = e.target as HTMLInputElement;
15-
if (target.checked) {
16-
selectedItems = [...selectedItems, items[index]];
17-
} else {
18-
selectedItems = selectedItems.filter((item) => item !== items[index]);
19-
}
20-
};
8+
let selected = structuredClone(group.children);
219
2210
const selectAll = () => {
23-
selectedItems = items;
11+
Object.keys(selected).forEach((key) => (selected[key].selected = true));
2412
};
2513
2614
const selectNone = () => {
27-
selectedItems = [];
15+
Object.keys(selected).forEach((key) => (selected[key].selected = false));
2816
};
2917
3018
const onSave = () => {
31-
handleSave(group, selectedItems);
19+
handleSave({
20+
...group,
21+
children: selected
22+
});
3223
};
3324
3425
const onCancel = () => {
35-
selectedItems = selected;
26+
console.log(selected, group.children);
27+
selected = structuredClone(group.children);
3628
handleCancel();
3729
};
3830
39-
const gridClass = (items: FacetOption[]) => {
31+
const gridClass = (items: any[]) => {
4032
if (items.length >= 50) {
4133
return 'grid-cols-5';
4234
} else if (items.length >= 30) {
@@ -51,24 +43,20 @@
5143

5244
<div class="p-5 rounded-md bg-surface-50 dark:bg-surface-800 border-primary-500 border-2">
5345
<!-- Header -->
54-
<h2 class="text-xl font-semibold">{group}</h2>
46+
<h2 class="text-xl font-semibold">{group.displayName}</h2>
5547

5648
<!-- Items -->
5749
<div
5850
class="grid {gridClass(
59-
items
51+
Object.keys(selected)
6052
)} !gap-x-20 gap-y-2 py-10 px-2 max-h-[1000px] overflow-x-auto max-w-6xl"
6153
>
62-
{#each items as item, index}
54+
{#each Object.keys(selected) as key}
6355
<label class="flex gap-3 items-center">
64-
<input
65-
type="checkbox"
66-
class="checkbox"
67-
value={item.value}
68-
on:click={(e) => handleCheck(e, index)}
69-
checked={selectedItems.includes(item)}
70-
/>
71-
<span class="whitespace-nowrap break-before-avoid break-after-avoid">{item.value}</span>
56+
<input type="checkbox" class="checkbox" bind:checked={selected[key].selected} />
57+
<span class="whitespace-nowrap break-before-avoid break-after-avoid"
58+
>{selected[key].displayName}</span
59+
>
7260
</label>
7361
{/each}
7462
</div>

src/lib/models/Models.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export interface Columns {
104104
[key: string]: Column;
105105
}
106106

107-
// Server config type for the table
107+
// Server config type for the table
108108
export type ServerConfig = {
109109
baseUrl?: string; // Base URL for server-side table
110110
sendModel?: Send; // Send model for server-side table
@@ -191,14 +191,29 @@ export type Filter = {
191191
value: string | number | Date | boolean;
192192
};
193193

194-
export type FacetOption = {
195-
value: string;
194+
export interface FacetOption {
195+
name: string;
196+
displayName: string;
196197
count?: number;
197-
};
198+
}
198199

199-
export type FacetGroup = {
200-
[key: string]: FacetOption[];
201-
};
200+
export interface FacetGroup {
201+
name: string;
202+
displayName: string;
203+
children: FacetOption[];
204+
count?: number;
205+
}
206+
207+
export interface SelectedFacetOption extends FacetOption {
208+
selected: boolean;
209+
}
210+
211+
export interface SelectedFacetGroup extends Omit<FacetGroup, 'children'> {
212+
selected: boolean;
213+
children: {
214+
[key: string]: SelectedFacetOption;
215+
};
216+
}
202217

203218
export class Send {
204219
id: number;
@@ -237,14 +252,13 @@ export class Receive {
237252
export class errorType {
238253
statusText: string;
239254
status: number;
240-
error:string;
241-
stackTrace:string
255+
error: string;
256+
stackTrace: string;
242257

243258
constructor() {
244-
this.statusText = "";
259+
this.statusText = '';
245260
this.status = 0;
246-
this.error = "";
247-
this.stackTrace = "";
261+
this.error = '';
262+
this.stackTrace = '';
248263
}
249-
250-
};
264+
}

0 commit comments

Comments
 (0)