Skip to content

Commit 7747524

Browse files
committed
refactor: refactor CTable component
- fix filtering, - delete escaping html when sorting, - add keys in v-for loops, - small changes in behavior - make code more readable
1 parent 79dce4a commit 7747524

File tree

1 file changed

+82
-55
lines changed

1 file changed

+82
-55
lines changed

src/components/Table/CTable.vue

Lines changed: 82 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</div>
1717

1818
<div
19-
v-if="havePaginationMenu()"
19+
v-if="optionsRow !== 'noPagination'"
2020
class="c-col-sm-6 c-p-0"
2121
:class="optionsRow === 'noFilter' ? 'c-offset-sm-6' : ''"
2222
>
@@ -29,7 +29,11 @@
2929
<option value="" selected disabled hidden>
3030
{{perPageItems}}
3131
</option>
32-
<option v-for="number in [5,10,20,50]" val="number">
32+
<option
33+
v-for="(number, key) in [5,10,20,50]"
34+
:val="number"
35+
:key="key"
36+
>
3337
{{number}}
3438
</option>
3539
</select>
@@ -49,6 +53,7 @@
4953
@click="changeSort(rawColumnNames[index], index)"
5054
:class="[headerClass(index), sortingIconStyles]"
5155
:style="headerStyles(index)"
56+
:key="index"
5257
>
5358
<slot :name="`${rawColumnNames[index]}-header`">
5459
<div class="c-d-inline">{{name}}</div>
@@ -80,14 +85,14 @@
8085
/>
8186
</th>
8287
<template v-for="(colName, index) in rawColumnNames" >
83-
<th :class="headerClass(index)">
88+
<th :class="headerClass(index)" :key="index">
8489
<slot :name="`${rawColumnNames[index]}-filter`">
8590
<input
8691
v-if="!fields || !fields[index].noFilter"
8792
class="c-w-100 c-table-filter"
8893
@input="addColumnFilter(colName, $event.target.value)"
8994
:value="columnFilter[colName]"
90-
></input>
95+
/>
9196
</slot>
9297
</th>
9398
</template>
@@ -100,6 +105,7 @@
100105
<tr
101106
:class="item._classes" :tabindex="bodyStyle ? 0 : null"
102107
@click="rowClicked(item, itemIndex + firstItemIndex)"
108+
:key="itemIndex"
103109
>
104110
<slot
105111
v-if="indexColumn"
@@ -119,7 +125,11 @@
119125
:item="item"
120126
:index="itemIndex + firstItemIndex"
121127
/>
122-
<td v-else :class="cellClass(item, colName, index)">
128+
<td
129+
v-else
130+
:class="cellClass(item, colName, index)"
131+
:key="index"
132+
>
123133
{{item[colName]}}
124134
</td>
125135
</template>
@@ -128,6 +138,7 @@
128138
v-if="$scopedSlots.details"
129139
class="c-p-0"
130140
style="border:none !important"
141+
:key="'details' + itemIndex"
131142
>
132143
<td
133144
:colspan="colspan"
@@ -168,6 +179,7 @@
168179
@click="changeSort(rawColumnNames[index], index)"
169180
:class="[headerClass(index), sortingIconStyles]"
170181
:style="headerStyles(index)"
182+
:key="index"
171183
>
172184
<slot :name="`${rawColumnNames[index]}-header`">
173185
<div class="c-d-inline">{{name}}</div>
@@ -269,25 +281,34 @@ export default {
269281
column: this.defaultSorter.column || null,
270282
asc: this.defaultSorter.asc || true
271283
},
272-
firstItemIndex: 0,
273284
page: this.activePage || 1,
274285
perPageItems: this.perPage,
275286
passedItems: this.items || []
276287
}
277288
},
278289
computed: {
279290
columnFiltered () {
280-
let items = this.passedItems
291+
let items = this.passedItems.slice()
281292
Object.keys(this.columnFilter).forEach(key => {
282-
items = items.filter(item => String(item[key]).toLowerCase().includes(this.columnFilter[key].toLowerCase()))
293+
items = items.filter(item => {
294+
const columnFilter = this.columnFilter[key].toLowerCase()
295+
return String(item[key]).toLowerCase().includes(columnFilter)
296+
})
283297
})
284298
return items
285299
},
300+
filterableCols () {
301+
return this.rawColumnNames.filter(name => {
302+
return this.generatedColumnNames.includes(name)
303+
})
304+
},
286305
tableFiltered () {
287-
let items = this.columnFiltered
306+
let items = this.columnFiltered.slice()
288307
if (this.tableFilter) {
308+
const filter = this.tableFilter.toLowerCase()
309+
const hasFilter = (item) => String(item).toLowerCase().includes(filter)
289310
items = items.filter(item => {
290-
return Object.keys(item).filter(key => String(item[key]).toLowerCase().includes(this.tableFilter.toLowerCase())).length
311+
return this.filterableCols.filter(key => hasFilter(item[key])).length
291312
})
292313
}
293314
return items
@@ -297,41 +318,46 @@ export default {
297318
if (!col || !this.rawColumnNames.includes(col)) {
298319
return this.tableFiltered
299320
}
300-
//if numbers should be sorted by numeric value they all have to be valid js numbers
321+
//if values in column are to be sorted by numeric value they all have to be type number
301322
const flip = this.sorter.asc ? 1 : -1
302-
return this.tableFiltered.sort((a,b) => {
303-
//escape html
304-
let c = typeof a[col] === 'string' ? a[col].replace(/<(?:.|\n)*?>/gm, '') : a[col]
305-
let d = typeof b[col] === 'string' ? b[col].replace(/<(?:.|\n)*?>/gm, '') : b[col]
306-
// if (typeof c !== typeof d) {
307-
// c = String(c)
308-
// d = String(d)
309-
// }
310-
return (c > d) ? 1 * flip : ((d > c) ? -1 * flip : 0)
323+
return this.tableFiltered.slice().sort((a,b) => {
324+
return (a[col] > b[col]) ? 1 * flip : ((b[col] > a[col]) ? -1 * flip : 0)
311325
})
312326
},
327+
firstItemIndex () {
328+
return (this.computedPage - 1) * this.perPageItems || 0
329+
},
330+
paginatedItems () {
331+
return this.sortedItems.slice(
332+
this.firstItemIndex,
333+
this.firstItemIndex + this.perPageItems
334+
)
335+
},
313336
currentItems () {
314-
if (this.computedPage) {
315-
this.firstItemIndex = (this.computedPage - 1) * this.perPageItems
316-
return this.sortedItems.slice(this.firstItemIndex, this.firstItemIndex + this.perPageItems)
317-
}
318-
return this.sortedItems
337+
return this.computedPage ? this.paginatedItems : this.sortedItems
319338
},
320339
totalPages () {
321340
return Math.ceil((this.sortedItems.length)/ this.perPageItems) || 1
322341
},
323342
computedPage () {
324343
return this.pagination ? this.page : this.activePage
325344
},
345+
generatedColumnNames () {
346+
return Object.keys(this.passedItems[0]).filter(el => el.charAt(0) !== '_')
347+
},
326348
rawColumnNames () {
327-
if (this.fields)
328-
return typeof this.fields[0] === 'object' ? this.fields.map(el => el.key) : this.fields
329-
return Object.keys(this.currentItems[0]).filter(el => el.charAt(0) !== '_')
349+
if (this.fields) {
350+
return this.fields.map(el => el.key || el)
351+
}
352+
return this.generatedColumnNames
330353
},
331354
columnNames () {
332-
if (this.fields)
333-
return this.fields.map(el => el.label !== undefined ? el.label : this.columnNamePretify(el.key || el))
334-
return this.rawColumnNames.map(el => this.columnNamePretify(el))
355+
if (this.fields) {
356+
return this.fields.map(f => {
357+
return f.label !== undefined ? f.label : this.pretifyName(f.key || f)
358+
})
359+
}
360+
return this.rawColumnNames.map(el => this.pretifyName(el))
335361
},
336362
tableClasses () {
337363
return [
@@ -356,26 +382,28 @@ export default {
356382
return !this.noSorting ? 'c-position-relative c-pr-4' : ''
357383
},
358384
colspan () {
359-
return this.indexColumn ? this.rawColumnNames.length + 1 : this.rawColumnNames.length
385+
return this.rawColumnNames.length + (this.indexColumn ? 1 : 0)
360386
},
361387
topLoadingPosition () {
362388
const headerHeight = (this.filterRow ? 38 : 0) + ( this.small ? 32 + 4 : 46 + 7)
363389
return `top:${headerHeight}px`
364390
},
365391
spinnerSize () {
366-
const size = this.small ? '1.4rem' : this.currentItems.length === 1 ? '2rem' : '3rem'
367-
return `width:${size};height:${size}`
392+
const size = this.small ? 1.4 : this.currentItems.length === 1 ? 2 : 3
393+
return `width:${size + 'rem'};height:${size + 'rem'}`
368394
},
369395
isFiltered () {
370-
return this.tableFilter || Object.keys(this.columnFilter).filter(key => {
371-
return this.columnFilter[key]
372-
}).length
396+
return this.tableFilter || Object.values(this.columnFilter).join('')
373397
}
374398
},
375399
watch: {
376400
items (val, oldVal) {
377-
if(val.length !== oldVal.length || JSON.stringify(val) !== JSON.stringify(oldVal))
401+
if (
402+
val.length !== oldVal.length ||
403+
JSON.stringify(val) !== JSON.stringify(oldVal)
404+
) {
378405
this.passedItems = val
406+
}
379407
},
380408
totalPages: {
381409
immediate: true,
@@ -405,35 +433,38 @@ export default {
405433
this.sorter.asc = true
406434
const inputs = this.$el.getElementsByClassName('c-table-filter')
407435
for(let input of inputs)
408-
input.value =''
436+
input.value = ''
409437
},
410-
columnNamePretify (name) {
411-
const withSpaces = name.replace(/[-_]/g, ' ')
412-
return withSpaces.split(' ')
413-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
414-
.join(' ')
438+
pretifyName (name) {
439+
return name.replace(/[-_]/g, ' ').split(' ').map(word => {
440+
return word.charAt(0).toUpperCase() + word.slice(1)
441+
}).join(' ')
415442
},
416443
cellClass (item, colName, index) {
417444
let classes = []
418-
if(item._cellClasses && item._cellClasses[colName])
445+
if (item._cellClasses && item._cellClasses[colName]) {
419446
classes.push(item._cellClasses[colName])
420-
if (this.fields && this.fields[index]._classes)
447+
}
448+
if (this.fields && this.fields[index]._classes) {
421449
classes.push(this.fields[index]._classes)
450+
}
422451
return classes
423452
},
424453
sortable (index) {
425454
return !this.noSorting && (!this.fields || !this.fields[index].noSorting)
426455
},
427456
headerClass (index) {
428-
return this.fields && this.fields[index]._classes ?
429-
this.fields[index]._classes : ''
457+
const fields = this.fields
458+
return fields && fields[index]._classes ? fields[index]._classes : ''
430459
},
431460
headerStyles (index) {
432461
let style = ''
433-
if(this.sortable(index))
462+
if (this.sortable(index)) {
434463
style += `cursor:pointer;`
435-
if(this.fields && this.fields[index] && this.fields[index]._style)
464+
}
465+
if (this.fields && this.fields[index] && this.fields[index]._style) {
436466
style += this.fields[index]._style
467+
}
437468
return style
438469
},
439470
rowClicked (item, index) {
@@ -456,12 +487,8 @@ export default {
456487
paginationChange (e) {
457488
this.$emit('pagination-change', e.target.value)
458489
this.perPageItems = Number(e.target.value)
459-
},
460-
havePaginationMenu () {
461-
return this.optionsRow !== 'noPagination' &&
462-
(this.pagination || this.$listeners['pages-change'])
463490
}
464-
},
491+
}
465492
}
466493
</script>
467494
<style scoped>

0 commit comments

Comments
 (0)