Skip to content

Commit 5086fc2

Browse files
committed
Rewrite value filter to be reactive to data
1 parent 65a0005 commit 5086fc2

File tree

3 files changed

+95
-53
lines changed

3 files changed

+95
-53
lines changed

demo/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<div class="mb-5">
88
<pivot
9-
:data="data"
9+
:data="asyncData"
1010
:fields="fields"
1111
:available-field-keys="availableFieldKeys"
1212
:row-field-keys="rowFieldKeys"

src/FieldLabel.vue

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
class="btn dropdown-toggle-split"
1818
:class="`btn-${variant}`"
1919
v-if="hasDropdown"
20-
@click="toggleShowSettings(field)">
21-
<svg v-if="!showSettings" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="caret-down" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" class="svg-inline--fa fa-caret-down fa-w-10"><path fill="currentColor" d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z" class=""></path></svg>
20+
@click="toggleShowDropdown(field)">
21+
<svg v-if="!showDropdown" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="caret-down" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" class="svg-inline--fa fa-caret-down fa-w-10"><path fill="currentColor" d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z" class=""></path></svg>
2222
<svg v-else aria-hidden="true" focusable="false" data-prefix="fas" data-icon="caret-up" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" class="svg-inline--fa fa-caret-up fa-w-10"><path fill="currentColor" d="M288.662 352H31.338c-17.818 0-26.741-21.543-14.142-34.142l128.662-128.662c7.81-7.81 20.474-7.81 28.284 0l128.662 128.662c12.6 12.599 3.676 34.142-14.142 34.142z" class=""></path></svg>
2323
</button>
2424
</div>
@@ -27,7 +27,7 @@
2727
<div
2828
class="field-dropdown-menu"
2929
v-if="hasDropdown"
30-
v-show="showSettings">
30+
v-show="showDropdown">
3131
<!-- Headers filter -->
3232
<template v-if="field.headerAttributeFilter">
3333
<h6 class="dropdown-header">Attributes</h6>
@@ -59,13 +59,13 @@
5959
type="checkbox"
6060
class="custom-control-input"
6161
:id="`checkbox-${field.key}-value-${index}`"
62-
v-model="value.checked">
62+
v-model="fieldValues[value]">
6363
<label class="custom-control-label" :for="`checkbox-${field.key}-value-${index}`">
64-
<slot v-if="field.valueFilterSlotName" :name="field.valueFilterSlotName" v-bind:value="value.label">
64+
<slot v-if="field.valueFilterSlotName" :name="field.valueFilterSlotName" v-bind:value="value">
6565
Missing slot <code>{{ field.valueFilterSlotName }}</code>
6666
</slot>
6767
<template v-else>
68-
{{ value.label }}
68+
{{ value }}
6969
</template>
7070
</label>
7171
</div>
@@ -78,7 +78,13 @@
7878

7979
<script>
8080
export default {
81+
model: {
82+
prop: 'fieldValues',
83+
},
8184
props: {
85+
fieldValues: {
86+
type: Object
87+
},
8288
field: {
8389
type: Object
8490
},
@@ -89,7 +95,7 @@ export default {
8995
},
9096
data: function() {
9197
return {
92-
showSettings: false
98+
showDropdown: false
9399
}
94100
},
95101
computed: {
@@ -98,8 +104,8 @@ export default {
98104
}
99105
},
100106
methods: {
101-
toggleShowSettings: function() {
102-
this.showSettings = !this.showSettings
107+
toggleShowDropdown: function() {
108+
this.showDropdown = !this.showDropdown
103109
}
104110
}
105111
}

src/Pivot.vue

Lines changed: 79 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
@start="start"
1919
@end="end">
2020
<div v-for="key in internal.availableFieldKeys" :key="key" class="field">
21-
<field-label :field="fieldFromKey(key)" variant="secondary" />
21+
<field-label :field="fieldsWithValues[key]" v-model="fieldValues[key]" variant="secondary">
22+
<!-- pass down scoped slots -->
23+
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope"><slot :name="slot" v-bind="scope"/></template>
24+
</field-label>
2225
</div>
2326
</draggable>
2427
</div>
@@ -44,7 +47,7 @@
4447
@start="start"
4548
@end="end">
4649
<div v-for="key in internal.colFieldKeys" :key="key" class="field">
47-
<field-label :field="fieldFromKey(key)">
50+
<field-label :field="fieldsWithValues[key]" v-model="fieldValues[key]">
4851
<!-- pass down scoped slots -->
4952
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope"><slot :name="slot" v-bind="scope"/></template>
5053
</field-label>
@@ -65,7 +68,7 @@
6568
@start="start"
6669
@end="end">
6770
<div v-for="key in internal.rowFieldKeys" :key="key" class="field">
68-
<field-label :field="fieldFromKey(key)">
71+
<field-label :field="fieldsWithValues[key]" v-model="fieldValues[key]">
6972
<!-- pass down scoped slots -->
7073
<template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope"><slot :name="slot" v-bind="scope"/></template>
7174
</field-label>
@@ -153,46 +156,69 @@ export default {
153156
}
154157
},
155158
data: function() {
156-
const fields = this.fields
157-
const valueFilterableFields = fields.filter(field => field.valueFilter)
159+
const fieldValues = {}
160+
this.fields.filter(field => field.valueFilter).forEach(field => {
161+
fieldValues[field.key] = {}
162+
})
163+
164+
return {
165+
internal: {
166+
availableFieldKeys: this.availableFieldKeys,
167+
rowFieldKeys: this.rowFieldKeys,
168+
colFieldKeys: this.colFieldKeys
169+
},
170+
fieldValues,
171+
dragging: false,
172+
showSettings: true
173+
}
174+
},
175+
computed: {
176+
// Fields with values extracted from data (if field has valueFilter)
177+
fieldsWithValues: function() {
178+
// Create object: field.key => field
179+
const fieldsWithValues = {}
180+
181+
this.fields.forEach(field => {
182+
fieldsWithValues[field.key] = field
183+
})
158184
159-
// If at least one field has valueFilter, get values from data
160-
if (valueFilterableFields.length > 0) {
161-
// Create new set for each field with value filter
185+
// Add valuesSet
186+
const valueFilterableFields = this.fields.filter(field => field.valueFilter)
187+
188+
// Create valuesSet for each value filterable field
162189
valueFilterableFields.forEach(field => {
163-
field.valuesSet = new Set()
190+
fieldsWithValues[field.key].valuesSet = new Set()
164191
})
165192
166193
// Iterate on data once
167194
this.data.forEach(item => {
168195
valueFilterableFields.forEach(field => {
169-
field.valuesSet.add(field.getter(item))
196+
fieldsWithValues[field.key].valuesSet.add(field.getter(item))
170197
})
171198
})
172199
173-
// Create object with checked boolean for each value
200+
// Creates values sorted from valuesSet
174201
valueFilterableFields.forEach(field => {
175-
const values = []
176-
Array.from(field.valuesSet).sort(field.sort || naturalSort).forEach(value => {
177-
values.push({ label: value, checked: true })
178-
})
179-
180-
this.$set(field, 'values', values)
202+
fieldsWithValues[field.key].values = Array.from(fieldsWithValues[field.key].valuesSet).sort(field.sort || naturalSort)
181203
})
182-
}
183204
184-
return {
185-
internal: {
186-
fields,
187-
availableFieldKeys: this.availableFieldKeys,
188-
rowFieldKeys: this.rowFieldKeys,
189-
colFieldKeys: this.colFieldKeys
190-
},
191-
dragging: false,
192-
showSettings: true
193-
}
194-
},
195-
computed: {
205+
return fieldsWithValues
206+
},
207+
// Fields selected values as set
208+
fieldsSelectedValues: function() {
209+
const fieldsSelectedValues = {}
210+
211+
for (let [key, valuesObject] of Object.entries(this.fieldValues)) {
212+
fieldsSelectedValues[key] = new Set()
213+
for (let [value, checked] of Object.entries(valuesObject)) {
214+
if (checked) {
215+
fieldsSelectedValues[key].add(value)
216+
}
217+
}
218+
}
219+
220+
return fieldsSelectedValues
221+
},
196222
// Pivot table props from Pivot props & data
197223
rowFields: function() {
198224
const rowFields = []
@@ -233,16 +259,12 @@ export default {
233259
// Remove items with unchecked values from value filters
234260
let remove = false
235261
236-
// TODO: optimize this (stop loop when found? build shared Sets before?)
237-
this.internal.fields.forEach(field => {
262+
// TODO: optimize this (stop loop when found?)
263+
this.fields.forEach(field => {
238264
if (field.valueFilter) {
239-
const selectedValuesSet = new Set()
240-
field.values.forEach(value => {
241-
if (value.checked) {
242-
selectedValuesSet.add(value.label)
243-
}
244-
})
245-
if (!selectedValuesSet.has(field.getter(item))) {
265+
const value = field.getter(item)
266+
const valueString = value ? value.toString() : 'undefined' // TODO: avoid values stringification (replace fieldValues hashes with arrays)
267+
if (!this.fieldsSelectedValues[field.key].has(valueString)) {
246268
remove = true
247269
}
248270
}
@@ -262,10 +284,6 @@ export default {
262284
}
263285
},
264286
methods: {
265-
// Get a field from its key
266-
fieldFromKey: function(key) {
267-
return this.internal.fields.find(field => field.key === key)
268-
},
269287
// Toggle settings
270288
toggleShowSettings: function() {
271289
this.showSettings = !this.showSettings
@@ -276,11 +294,29 @@ export default {
276294
},
277295
end: function() {
278296
this.dragging = false
297+
},
298+
// Update fieldValues
299+
updateFieldValues: function() {
300+
for (let [key, field] of Object.entries(this.fieldsWithValues)) {
301+
if (field.valueFilter) {
302+
field.valuesSet.forEach(value => {
303+
this.$set(this.fieldValues[key], value, true)
304+
})
305+
}
306+
}
279307
}
280308
},
281309
created: function() {
282310
// TODO: check if field keys are correctly set/without duplicates
283311
this.showSettings = this.defaultShowSettings
312+
},
313+
watch: {
314+
data: function() {
315+
this.updateFieldValues()
316+
}
317+
},
318+
created: function() {
319+
this.updateFieldValues()
284320
}
285321
}
286322
</script>

0 commit comments

Comments
 (0)