summaryrefslogtreecommitdiffstats
path: root/webapp/src/components/DataTable.vue
diff options
context:
space:
mode:
authorUdo Walter2019-02-28 20:26:08 +0100
committerUdo Walter2019-02-28 20:26:08 +0100
commit4dd3c027dc088f74c3bd6f3148b6d94eca8719d3 (patch)
treea4ecc11dcc56afa5a3e3658e12b05649506953bf /webapp/src/components/DataTable.vue
parent[webapp/groups] performance improvements (diff)
downloadbas-4dd3c027dc088f74c3bd6f3148b6d94eca8719d3.tar.gz
bas-4dd3c027dc088f74c3bd6f3148b6d94eca8719d3.tar.xz
bas-4dd3c027dc088f74c3bd6f3148b6d94eca8719d3.zip
[webapp/datatable] improve search performance
Diffstat (limited to 'webapp/src/components/DataTable.vue')
-rw-r--r--webapp/src/components/DataTable.vue172
1 files changed, 26 insertions, 146 deletions
diff --git a/webapp/src/components/DataTable.vue b/webapp/src/components/DataTable.vue
index bd97394..ab530fc 100644
--- a/webapp/src/components/DataTable.vue
+++ b/webapp/src/components/DataTable.vue
@@ -1,7 +1,6 @@
<i18n>
{
"en": {
- "search": "Search",
"all": "All",
"entries": "Entries",
"noResult": "No entries.",
@@ -12,7 +11,6 @@
"selected": "selected"
},
"de": {
- "search": "Suche",
"all": "Alle",
"entries": "Einträge",
"noResult": "Keine Einträge.",
@@ -29,41 +27,16 @@
<div>
<v-layout wrap :class="{ 'd-block': slim }">
<v-flex xs12 :md6="!slim" class="d-flex align-center search-wrapper" :class="{ 'slim-search-wrapper': slim }">
- <div class="search-field-wrapper">
- <div v-for="(s, index) in search" :key="index">
- <v-text-field
- color="primary"
- solo flat
- style="padding: 0: margin: 0;"
- class="search-field"
- :placeholder="$t('search')"
- :value="s.text.raw"
- @input="prerocessSearch(s, $event)"
- hide-details
- prepend-inner-icon="search"
- clearable
- ></v-text-field>
- <v-select
- v-if="!slim"
- solo flat
- class="column-select"
- v-model="s.key"
- :items="[ { text: $t('all'), key: null }, ...searchableHeaders ]"
- item-text="text"
- item-value="key"
- color="primary"
- hide-details
- :menu-props="{
- offsetY: true,
- left: true
- }"
- ></v-select>
- <template v-if="!slim">
- <v-btn v-if="index === 0" icon @click="newSearchField"><v-icon>add</v-icon></v-btn>
- <v-btn v-else icon @click="removeSearchField(s)"><v-icon>remove</v-icon></v-btn>
- </template>
- </div>
- </div>
+ <data-table-search
+ ref="search"
+ @filter="filteredRows = $event"
+ :rows="sortedRows"
+ :headers="headers"
+ :regex="regex"
+ :case-sensitive="caseSensitive"
+ :only-show-selected="onlyShowSelected"
+ :slim="slim"
+ ></data-table-search>
<v-divider :class="{ 'hidden-md-and-up': !slim }"></v-divider>
<v-divider v-if="!slim" vertical class="hidden-sm-and-down"></v-divider>
</v-flex>
@@ -173,9 +146,13 @@
</template>
<script>
+import DataTableSearch from '@/components/DataTableSearch'
export default {
name: 'DataTable',
+ components: {
+ DataTableSearch
+ },
props: {
headers: {
type: Array
@@ -206,7 +183,6 @@ export default {
data () {
return {
selected: [],
- search: [{ text: { raw: '', upper: '', regex: new RegExp(), regexCI: new RegExp() }, key: null }],
internalRowCount: 10,
selectState: 0,
headerSortState: {},
@@ -215,14 +191,10 @@ export default {
onlyShowSelected: false,
lastSelectIndex: null,
shiftKey: false,
- filteredRows: [],
- filteringLoop: { running: false, cancel: () => true }
+ filteredRows: []
}
},
computed: {
- searchableHeaders () {
- return this.headers.filter(h => h.text !== undefined)
- },
computedMinWidth () {
if (this.minWidth) return this.minWidth
if (this.slim) return '200px'
@@ -236,24 +208,19 @@ export default {
if (this.computedRowCount > 0) style.height = (Math.max(Math.min(this.computedRowCount, this.filteredRows.length), 1) * 48 + 58) + 'px'
return style
},
- dataKeys () { return this.headers.map(x => x.key) },
rows () {
return Object.freeze(this.items.map(item => ({ data: item, selected: false, id: item.id })))
},
sortedRows () {
const rows = this.rows.slice(0)
for (let key in this.headerSortState) {
+ if (!this.headers.some(header => header.key === key)) continue
+ console.log('asdf')
const direction = this.headerSortState[key]
if (direction === 'asc') rows.sort((a, b) => String(a.data[key]).localeCompare(String(b.data[key])))
if (direction === 'desc') rows.sort((b, a) => String(a.data[key]).localeCompare(String(b.data[key])))
}
- return Object.freeze(rows)
- },
- filterFunction () {
- if (this.regex && this.caseSensitive) return (s, str) => s.text.regex.test(str)
- else if (this.regex && !this.caseSensitive) return (s, str) => s.text.regexCI.test(str)
- else if (!this.regex && this.caseSensitive) return (s, str) => str.indexOf(s.text.raw) !== -1
- else if (!this.regex && !this.caseSensitive) return (s, str) => str.toUpperCase().indexOf(s.text.upper) !== -1
+ return Object.freeze(rows)
}
},
watch: {
@@ -267,24 +234,6 @@ export default {
this.processSelected(newValue)
}
},
- sortedRows () {
- this.filterRows()
- },
- regex () {
- this.filterRows()
- },
- caseSensitive () {
- this.filterRows()
- },
- onlyShowSelected () {
- this.filterRows()
- },
- search: {
- deep: true,
- handler () {
- this.filterRows()
- }
- },
filteredRows () {
this.lastSelectIndex = null
this.calcSelectState()
@@ -292,11 +241,18 @@ export default {
},
methods: {
resetData () {
+ if (this.$refs.search) this.$refs.search.resetData()
Object.assign(this.$data, this.$options.data.call(this))
this.processSelected(this.value)
this.$emit('input', this.selected)
if (this.$refs.scroller) this.$refs.scroller.$el.scrollTop = 0
},
+ resetSearch () {
+ if (this.$refs.search) this.$refs.search.resetData()
+ this.processSelected(this.value)
+ this.$emit('input', this.selected)
+ if (this.$refs.scroller) this.$refs.scroller.$el.scrollTop = 0
+ },
selectItem (row, index) {
// Select or deselect this row
const selected = row.selected = !row.selected
@@ -367,31 +323,6 @@ export default {
}
this.selectState = seenTrue && seenFalse ? 1 : seenTrue && !seenFalse ? 2 : 0
},
- prerocessSearch (s, raw) {
- if (typeof raw !== 'string') raw = ''
- const tmp = { raw, upper: raw.toUpperCase() }
- try {
- tmp.regex = new RegExp(raw)
- tmp.regexCI = new RegExp(raw, 'i')
- } catch (e) {
- tmp.regex = { test: () => false }
- tmp.regexCI = { test: () => false }
- }
- s.text = tmp
- },
- newSearchField () {
- this.search.push({ text: {
- raw: '',
- upper: '',
- regex: new RegExp(),
- regexCI: new RegExp()
- },
- key: null })
- },
- removeSearchField (s) {
- this.search.splice(this.search.indexOf(s), 1)
- window.dispatchEvent(new Event('scroll'))
- },
toggleHeaderSort (header) {
if (header.text === undefined) return
const state = this.headerSortState[header.key]
@@ -418,40 +349,9 @@ export default {
row.selected = false
}
})
- if (this.onlyShowSelected) this.filterRows()
+ if (this.onlyShowSelected && this.$refs.search) this.$refs.search.filterRows()
if (this.filteredRows) this.calcSelectState()
- },
- filterRows () {
- // Cancel the last filtering loop
- this.filteringLoop.cancel()
-
- // Skip filtering if all search strings are empty
- if (this.search.every(s => s.text.raw === '') && !this.onlyShowSelected) {
- this.filteredRows = Object.freeze(this.sortedRows)
- return
- }
-
- // Filter rows using a time slicing loop
- const result = []
- this.filteringLoop = this.$loop(this.sortedRows.length, 1000,
- i => {
- const row = this.sortedRows[i]
- if ((!this.onlyShowSelected || (this.onlyShowSelected && row.selected)) && this.search.every(s => {
- if (s.key === null) {
- return this.dataKeys.some(key => {
- return this.filterFunction(s, String(row.data[key]))
- })
- } else {
- return this.filterFunction(s, String(row.data[s.key]))
- }
- })) result.push(row)
- },
- () => { this.filteredRows = result }
- )
}
- },
- created () {
- this.filterRows()
}
}
</script>
@@ -483,26 +383,6 @@ export default {
align-items: stretch;
}
-.search-field-wrapper {
- display: flex;
- flex-direction: column;
-}
-
-.search-field-wrapper > div {
- display: flex;
- align-items: center;
-}
-
-.search-field {
- margin: 0;
- padding: 0;
- font-family: 'Roboto Mono';
-}
-
-.column-select {
- flex: 0 1 0
-}
-
.rowcount-select {
padding: 0;
display: inline-block;