Last active
September 22, 2017 19:19
-
-
Save Herteby/c25895b6b3b9cf4060c577d8ab487f2b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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