/
DataTable.svelte
148 lines (142 loc) · 3.42 KB
/
DataTable.svelte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<script>
import { createEventDispatcher } from 'svelte';
import FilterEditor from './FilterEditor.svelte';
const dispatch = createEventDispatcher();
const refresh = (newState) => {
dispatch('displayStateChanged', newState);
};
export let dataSource;
export let fields;
export let schema = {};
export let displayState = {
sort: undefined,
desc: false,
filters: {}
};
export let editBaseUrl = null;
export let extraRowActionButtons = null;
const headerClick = (field) => () => {
const newSort = field;
const newDesc = displayState.sort === newSort ? !displayState.desc : false;
refresh({ ...displayState, sort: newSort, desc: newDesc });
};
const newFilterValue =
(fieldName) =>
({ detail: newValue }) => {
refresh({
...displayState,
filters: {
...displayState.filters,
// Set the value for this field's filter, keeping
// the pre-configured filter type
[fieldName]: { filter: fields[fieldName].filter, value: newValue }
}
});
};
</script>
<table class="border-separate w-full">
<tr>
{#each Object.keys(fields) as f}
<th class={fields[f].class} on:click={headerClick(f)}
>{schema[f] || f} {displayState.sort === f ? (displayState.desc ? '↓' : '↑') : ''}</th
>
{/each}
<th class="action">
{#if editBaseUrl}
<a href={editBaseUrl} alt="Create"><span class="fa fa-star-o" /></a>
{:else}
<div> </div>
{/if}
</th>
</tr>
<tr class="filterRow">
{#each Object.keys(fields) as f}
{@const field = fields[f]}
{@const filter = field.filter}
<td class={field.class}>
{#if filter && filter !== 'none'}
<FilterEditor
{filter}
value={displayState.filters[f]?.value}
on:newValueConfirmed={newFilterValue(f)}
/>
{/if}
</td>
{/each}
<td class="action">
<div> </div>
</td>
</tr>
{#await dataSource}
<tr class="placeholder"><td colspan="2">Waiting for data</td></tr>
{:then dataSourceContent}
{#if dataSourceContent && Array.isArray(dataSourceContent)}
{#each dataSourceContent as item}
<tr>
{#each Object.keys(fields) as f}
<td class={fields[f].class}>{item[f]}</td>
{/each}
<td class="action">
{#if editBaseUrl}
<a href="{editBaseUrl}/{item[schema['$$idField']]}" alt="Edit"
><span class="fa fa-edit" /></a
>
{/if}
{#if extraRowActionButtons}
<svelte:component this={extraRowActionButtons} row={item} />
{/if}
</td>
</tr>
{:else}
<tr class="placeholder empty">
<td colspan={Object.keys(fields).length}>Empty list</td>
</tr>
{/each}
{:else}
<tr class="placeholder debugging">
<td colspan={Object.keys(fields).length}>
Unknown dataSourceContent content (debugging): <code
>{JSON.stringify(dataSourceContent)}</code
>
</td>
</tr>
{/if}
{:catch e}
<tr class="placeholder error">
<td colspan={Object.keys(fields).length}>Error: {e}</td>
</tr>
{/await}
</table>
<style lang="postcss">
td,
th {
@apply px-2 py-1;
}
th {
@apply bg-blue-300 cursor-pointer select-none;
}
td {
@apply border border-gray-200;
}
tr.placeholder {
@apply bg-gray-200;
}
tr.empty {
@apply bg-yellow-200;
td {
@apply text-center p-4;
}
}
tr.error {
@apply bg-red-200 font-bold;
}
tr.filterRow > td {
@apply bg-red-200;
}
.action {
@apply bg-green-200 text-center flex gap-1;
}
.action a {
@apply border-2 rounded bg-white px-2 py-0.5 hover:bg-orange-200;
}
</style>