Skip to content

Instantly share code, notes, and snippets.

@Herteby
Last active September 22, 2017 19:19
Show Gist options
  • Save Herteby/c25895b6b3b9cf4060c577d8ab487f2b to your computer and use it in GitHub Desktop.
Save Herteby/c25895b6b3b9cf4060c577d8ab487f2b to your computer and use it in GitHub Desktop.
<template>
<div class="virtual-table" :class="{clickableRows:!!($listeners && $listeners.click)}">
<div class="tableHead" ref="head">
<template v-if="search">
<icon fa="search"></icon><input class="search" v-model="searchString" placeholder="Search" ref="search" autofocus>
</template>
<label v-for="filter in savedFilters" class="filter" ><checkbox v-model="filter.enabled">{{filter.display | tra}}</checkbox></label>
<div class="count">Results: {{typeof fullCount == 'number' ? fullCount.toLocaleString() : fullCount}}</div>
<div class="fields" ref="fields">
<div
v-for="field, n in savedFields"
v-if="field.name"
:key="field.key"
:class="{sortable:field.key}"
@click="changeSort(field)"
:style="{width:$get(row, n) + 'px'}"
>
{{field.name | tra}}
<span v-if="field.sort === 1"><icon fa="chevron-down" style="transform:scale(0.7)"></icon></span>
<span v-else-if="field.sort === -1"><icon fa="chevron-up" style="transform:scale(0.7)"></icon></span>
</div>
</div>
</div>
<div :style="{height:headHeight + 'px'}"></div>
<virtual-scroller
v-if="items.$readyOnce && items.$count"
pageMode
contentTag="table"
:items="buffer"
:keyField="false"
:itemHeight="itemHeight"
@update="update"
ref="scroller"
>
<template scope="props">
<tr :class="{odd:props.itemIndex % 2, ready:!!props.item}" @click="$emit('click',props.item)">
<template v-for="field in savedFields" v-if="field.name">
<td v-if="$scopedSlots[field.name]">
<slot v-if="props.item" :name="field.name" :item="props.item" :index="props.itemIndex" :match="match"></slot>
</td>
<td v-else-if="$get(props, 'item.' + field.key) instanceof Date">{{props.item[field.key], 'time' | niceDate}}</td>
<td v-else v-html="match($get(props, 'item.' + field.key))"></td>
</template>
</tr>
</template>
</virtual-scroller>
<h1 v-else-if="items.$ready" style="padding:20px">No results<template v-if="searchString"> for "{{searchString}}"</template></h1>
<loading v-else full></loading>
</div>
</template>
<script>
export default {
props:{
collection:{type:Mongo.Collection, required: true},
fields:{type:Array, required:true},
search:{type:Array},
filters:{type:Array},
baseQuery:{type:Object},
itemHeight:{type:Number},
sort2:{type:String, default:'_id'},
minimum:{type:Number, default:30},
regexPrefix:{type:String, default:'([^a-zA-Z0-9]|^)'}
},
data(){
return {
savedFilters:_.cloneDeep(this.filters),
savedFields:_.cloneDeep(this.fields),
filter:true,
showQuery:false,
indexes:{
start:0,
end:this.minimum
},
buffer:[],
searchString:'',
fullCount:undefined,
row:undefined,
headHeight:0,
listener:undefined
}
},
mounted(){
this.listener = () => {
this.headHeight = this.$refs.head.offsetHeight
}
addEventListener('resize', this.listener)
this.headHeight = this.$refs.head.offsetHeight
this.$refs.search && this.$refs.search.focus()
},
destroyed(){
removeEventListener('resize', this.listener)
},
watch:{
items:{
handler(items){
if(items.$fullCount !== false && items.$fullCount !== this.fullCount){
this.fullCount = items.$fullCount
this.buffer = new Array(items.$fullCount)
}
if(items.$ready){
for(let i = 0; i <= this.indexes.end - this.indexes.start && i < items.length; i++){
this.$set(this.buffer, i + this.indexes.start, items[i])
}
}
}, deep:true
}
},
methods:{
update:_.throttle(function(indexes){ //doing some stuff to reduce the number of subscription updates
let chunkSize = 10
indexes = {
start: Math.floor(indexes.startIndex / chunkSize) * chunkSize,
end: Math.ceil(indexes.endIndex / chunkSize) * chunkSize + chunkSize
}
if(indexes.end < this.minimum){
indexes.end = this.minimum
}
if(this.indexes.start !== indexes.start || this.indexes.end !== indexes.end){
this.indexes = indexes
}
this.$refs.scroller.updateVisibleItems()
//field widths
let row = _.get(this.$el.getElementsByTagName('tr'), '0.children')
this.row = _.map(row, 'offsetWidth')
}, 250),
changeSort(field){
if(_.isNumber(field.sort)){
this.$set(field, 'sort', 0 - field.sort)
} else if(field.sort !== false){
_.each(this.savedFields, sibling => {
if(sibling.sort !== false)
this.$set(sibling, 'sort', null)
})
this.$set(field, 'sort', 1)
}
},
match(string){
if(this.searchString && typeof string == 'string'){
return string.replace(new RegExp(this.regexPrefix + this.searchString, 'ig'), match => (match[0] == ' ' ? ' ' : '') + '<mark>' + match.trim() + '</mark>')
} else {
return string
}
}
},
provide(){
return {
match:this.match
}
},
grapher:{
items(){
let query = _.cloneDeep(this.baseQuery || {})
query.$options = {
sort:{},
skip:this.indexes.start,
limit:this.indexes.end ? this.indexes.end - this.indexes.start : 1
}
//go through the fields and add them to the query
_.each(this.savedFields, (field) => {
_.set(query, field.key, 1)
if(typeof field.sort == 'number'){
query.$options.sort[field.key] = field.sort
}
})
query.$options.sort[this.sort2] = 1
query.$filters = query.$filters || {}
//text search
if(this.searchString){
query.$filters.$or = query.$filters.$or || []
_.each(this.search, field => {
query.$filters.$or.push({[field]:{$regex:this.regexPrefix + this.searchString, $options:'i'}})
})
}
//toggleable filters
let filters = _.compact(_.map(this.savedFilters, filter => {
return filter.enabled ? filter.on : filter.off
}))
if(!_.isEmpty(filters))
query.$filters.$and = filters
return {
collection:this.collection,
query:query,
fullCount:true
}
}
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment