Skip to content

Instantly share code, notes, and snippets.

@PrimozRome
Created September 3, 2016 08:38
Show Gist options
  • Save PrimozRome/5250ef06f66e6a9ea70914fa50919955 to your computer and use it in GitHub Desktop.
Save PrimozRome/5250ef06f66e6a9ea70914fa50919955 to your computer and use it in GitHub Desktop.
<template>
<div id="datagrid-template" class="ui segment auto-overflow" :class="{ 'loading': loading }">
<table id="{{ id }}" class="table table-hover table-condensed table-ultra-condensed datagrid no-margin">
<thead>
<tr>
<th class="datagrid-toggle-column" v-if="allowSelection">
<div class="checkbox check-success no-margin">
<input type="checkbox" id="allrows" name="allrows" v-model="selectAll">
<label for="allrows" class="no-margin"></label>
</div>
</th>
<th v-for="(index, column) in columns"
:style="{ width: getCellWidth(column) }"
class="{{ column.class }}"
v-if="column.visible"
nowrap="">
<div class="control-group">
<div class="datagrid-header control control-fill" @click="sortBy(column)">
<span>{{ column.label }}</span>
<i class="fa"
:class="{ 'fa-angle-up': sortingDirection === 1, 'fa-angle-down': sortingDirection === -1 }"
v-show="sortingKey === column.key">
</i>
</div>
</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(index, row) in data"
:class="{ 'selected': selectedRows.indexOf(row) !== -1 }"
@click="addRowToSelection(index)">
<td class="datagrid-toggle-column" v-if="allowSelection">
<div class="checkbox check-success">
<input
type="checkbox"
id="{{ getControlId(groupName, index) }}"
name="{{ getControlId(groupName, index) }}"
:value="row"
v-model="selectedRows">
<label for="{{ getControlId(groupName, index) }}" class="no-margin"></label>
</div>
</td>
<td v-for="(indexCol, column) in columns"
:class="[column.class, isCellSelected(index, indexCol) ? 'selected' : '', (row == editedRow && column == editedColumn) ? 'editing' : '', (row == editedRow && column == editedColumn && column.form.type == 'select') ? 'overflow-visible' : '']"
@dblclick="editCell(row, column)"
v-if="column.visible"
:nowrap="column.nowrap ? true : null">
<partial :name="getCellTemplate(column)"></partial>
<formelement
v-if="allowEdit && row == editedRow"
:value.sync="editedCellValue"
:type="column.form.type"
:placeholder="column.form.placeholder"
:name="column.form.name"
:required="column.form.required"
:resource="column.form.resource"
:preload="column.form.preload"
:multiple="column.form.multiple"
:create="column.form.create"
:animation="column.form.animation"
:value-key="column.form.valueKey"
:text-key="column.form.textKey"
:add-label="column.form.addLabel"
:helper-message="column.form.helperMessage"
:no-results-label="column.form.noResultsLabel"
:class="['editCell', 'overflow-visible']"
v-cell-focus="row == editedRow && column == editedColumn"
@blur="doneEdit(row, column, index)"
@keydown.enter.stop="doneEdit(row, column, index)"
@keydown.esc.stop="cancelEdit(row, column, index)">
</td>
</tr>
</tbody>
<tfoot>
<tr>
<th v-if="allowSelection"></th>
<th
v-for="(index, column) in columns"
class="{{ column.class }}"
v-if="column.visible">
<div v-if="column.showSum">
{{ calculateSum(column) }}
</div>
</th>
</tr>
</tfoot>
</table>
<div class="row no-margin">
<div class="col-md-7 col-sm-12 p-t-10 p-l-20 text-center-xs text-center-sm">
<p class="normal-text">
<span>Showing
<strong>{{ showingFrom }} to {{ showingTo }}</strong> of {{ paginationData.total }} entries</span>
<span v-if="selectedRows.length">&nbsp;|&nbsp;</span>
<span v-if="selectedRows.length">
<strong>{{ selectedRows.length }}</strong> rows selected
<a @click.prevent="resetSelection()" href="#">remove selection</a>
</span>
<span v-if="dataFilter">&nbsp;|&nbsp;</span>
<span v-if="dataFilter">
Filtering on <strong>{{ dataFilter }}</strong>
<a @click.prevent="resetFilter()" href="#">remove filter</a>
</span>
<span v-if="groupingColumn">&nbsp;|&nbsp;</span>
<span v-if="groupingColumn">
Grouping on <strong>{{ groupingColumn.name }}</strong>
<a @click.prevent="resetGrouping()" href="#">remove grouping</a>
</span>
</p>
</div>
<div class="col-md-5 col-sm-12 p-t-10 p-r-20 p-b-10 text-center-sm text-center-xs text-right-md text-right-lg">
<pagination :pagination.sync="paginationData"></pagination>
</div>
</div>
</template>
<script>
import Vue from 'vue'
import pagination from './pagination'
import api from '../services/api'
import helpers from '../services/helpers'
import { groupBy, formatDate, formatMoney } from '../extensions/filters'
import formelement from './form/types/formelement'
Vue.filter('groupBy', groupBy)
Vue.filter('formatDate', formatDate)
Vue.filter('formatMoney', formatMoney)
Vue.partial('defaultGridCell', '<span class="cellContent">{{ formatData(column, getCellValue(row, column.key)) }}</span>')
Vue.partial('editableGridCell', '<input type="text" class="form-control" v-model="getCellValue(row, column.key)" lazy />')
Vue.partial('linkedGridCell', '<a @click.stop="" class="cellContent" v-link="{ path: [\'\', resource, getCellValue(row, \'id\')].join(\'/\') }"><partial name="defaultGridCell"></partial></a>')
export default {
components: {
pagination,
formelement
},
props: {
id: {
type: String,
required: true
},
resource: {
type: String,
required: true
},
includes: {
type: String,
required: false
},
columns: {
type: Array,
required: true
},
sortKey: {
type: String,
required: false
},
sortDirection: {
type: Number,
required: false,
default: 1
},
cellTemplate: {
type: String,
required: false,
default: 'defaultGridCell'
},
allowSelection: {
type: Boolean,
required: false,
default: false
},
allowEdit: {
type: Boolean,
required: false,
default: false
},
showDefaultOptions: {
type: Boolean,
required: false,
default: true
},
showAdvancedOptions: {
type: Boolean,
required: false,
default: false
}
},
computed: {
sortDirectionString: function () {
return this.sortingDirection === 1 ? 'ASC' : 'DESC'
},
columnSpan: function () {
return this.allowSelection ? this.columns.length + 1 : this.columns.length
},
showOptions: function () {
return this.showDefaultOptions || this.showAdvancedOptions
},
showFooter: function () {
return this.dataFilter || this.groupingColumn || this.selectedRows.length > 0
},
showingFrom: function () {
return this.paginationData.current_page * this.paginationData.per_page - this.paginationData.per_page + 1
},
showingTo: function () {
if (this.paginationData.current_page === this.paginationData.total_pages) {
return this.paginationData.total
} else {
return this.paginationData.current_page * this.paginationData.per_page
}
}
},
data: function () {
return {
data: [],
sortingKey: this.sortKey || this.columns[0].key,
sortingDirection: this.sortDirection,
groupingColumn: '',
dataFilter: null,
selectedRows: [],
selectAll: false,
paginationData: {
per_page: 25,
current_page: 1,
count: 0,
total_pages: 0,
total: 0
},
selectedRow: null,
selectedCol: null,
editedCell: null,
editedRow: null,
editedColumn: null,
loading: false,
editedCellValue: null
}
},
vuex: {
getters: {
searchQuery: state => state.globals.searchQuery,
filterQuery: state => state.globals.filterQuery
}
},
created: function () {
// console.log(this.columns)
window.addEventListener('keydown', this.keyboardHandler)
this.fetchData()
},
beforeDestroy: function () {
window.removeEventListener('keydown', this.keyboardHandler)
},
ready: function () {
},
methods: {
testing: function (data) {
console.log('from testing')
console.log(data)
},
getCellValue: function (row, key) {
console.time('Vue')
return key.split('.').reduce((a, b) => (a !== undefined) ? a[b] : a, row)
},
isCellSelected: function (rowIndex, colIndex) {
if (this.selectedRow === rowIndex && this.selectedCol === colIndex) {
return true
} else {
return false
}
},
editCell: function (row, column) {
if (!this.allowEdit) {
return false
}
let cell
if (column !== undefined && row !== undefined) {
cell = this.getCellValue(row, column.key)
}
this.editedCellValue = cell
this.editedRow = row
this.editedColumn = column
},
doneEdit: function (row, column, index) {
if (!this.editedRow && !this.editedColumn) {
return
}
this.editedRow = null
this.editedColumn = null
if (this.editedCellValue !== this.getCellValue(row, column.key)) {
this.loading = true
let params
if (column.hasOwnProperty('filterKey')) {
params = helpers.apiUpdateCell.convertData({}, column.filterKey, this.editedCellValue)
} else {
params = helpers.apiUpdateCell.convertData({}, column.key, this.editedCellValue)
}
if (column.hasOwnProperty('id')) {
params = helpers.apiUpdateCell.convertData(params, column.id, this.getCellValue(row, column.id))
}
api.updateData(this.resource + '/' + row.id, params).then((response) => {
helpers.apiUpdateCell.putData(column.key, this.editedCellValue, this.data[index])
this.loading = false
}, (err) => {
// handle error
console.log(err)
this.loading = false
})
}
/*
todo.title = todo.title.trim();
if (!todo.title) {
this.removeTodo(todo);
}
*/
},
cancelEdit: function (row, column, index) {
this.editedRow = null
this.editedColumn = null
// todo.title = this.beforeEditCache;
},
keyboardHandler: function (e) {
var inputs = ['input', 'select', 'button', 'textarea']
var activeElement = document.activeElement
if (activeElement && inputs.indexOf(activeElement.tagName.toLowerCase()) !== -1) {
return false
}
switch (e.keyCode) {
case 13:
e.preventDefault()
e.stopPropagation()
this.editCell(this.data[this.selectedRow], this.columns[this.selectedCol])
break
case 32:
if (this.selectedRow) {
e.preventDefault()
this.addRowToSelection(this.selectedRow)
}
break
case 37:
e.preventDefault()
this.keyLeftPressed()
break
case 38:
e.preventDefault()
this.keyUpPressed()
break
case 39:
e.preventDefault()
this.keyRightPressed()
break
case 40:
e.preventDefault()
this.keyDownPressed()
break
}
},
keyUpPressed: function () {
if (this.selectedRow > 0) {
this.selectedRow--
}
},
keyDownPressed: function () {
if (this.selectedRow === null) {
this.selectedRow = 0
this.selectedCol = 0
} else {
if (this.selectedRow < this.paginationData.count - 1) {
this.selectedRow++
}
}
},
keyRightPressed: function () {
if (this.selectedCol === null) {
this.selectedCol = 0
this.selectedRow = 0
} else {
if (this.selectedCol < this.columns.length - 1) {
this.selectedCol++
} else if (this.selectedCol === this.columns.length - 1) {
if (this.selectedRow < this.paginationData.count - 1) {
this.selectedCol = 0
this.selectedRow++
}
}
}
},
keyLeftPressed: function () {
if (this.selectedCol > 0) {
this.selectedCol--
} else if (this.selectedCol === 0) {
if (this.selectedRow > 0) {
this.selectedCol = this.columns.length - 1
this.selectedRow--
}
}
},
addRowToSelection: function (index) {
if (this.selectedRows.indexOf(this.data[index]) === -1) {
this.selectedRows.push(this.data[index])
} else {
this.selectedRows.splice(this.selectedRows.indexOf(this.data[index]), 1)
}
},
fetchData: function () {
this.loading = true
var params = {
page: this.paginationData.current_page,
per_page: this.paginationData.per_page,
include: this.includes,
sortby: this.sortingKey,
sortdir: this.sortDirectionString,
q: this.searchQuery,
filters: this.getFilterQueryData()
}
api.fetchData(this.resource, params).then((response) => {
this.data = response.data.data
this.paginationData = response.data.meta.pagination
this.loading = false
}, (err) => {
// handle error
console.log(err)
this.loading = false
})
},
getFilterQueryData: function () {
var result = {}
this.filterQuery.forEach(function (value) {
result[value.filterKey] = value.value
})
for (var i = 0; i < this.filterQuery.length; i++) {
result[this.filterQuery[i].filterKey] = this.filterQuery[i].form.value
}
return result
},
getCellTemplate: function (column) {
// return this.allowEdit ? 'editableGridCell' : (column.template || this.cellTemplate)
// console.log([column, column.template, this.cellTemplate])
return false ? 'editableGridCell' : (column.template || this.cellTemplate)
},
getCellWidth: function (column) {
if (!column.width) {
return
}
return column.width + (isNaN(column.width) ? '' : '%')
},
getControlId: function (groupName, index, suffix) {
return groupName + '-' + index + (suffix ? '-' + suffix : '')
},
sortBy: function (column) {
this.sortingKey = column.key
this.sortingDirection *= -1
this.fetchData()
return
},
groupBy: function (column) {
this.groupingColumn = column
},
resetFilter: function () {
this.dataFilter = null
},
resetGrouping: function () {
this.groupingColumn = null
},
resetSelection: function () {
this.selectedRows = []
this.selectAll = false
},
formatData: function (column, value) {
if (column.hasOwnProperty('filter')) {
var filter = Vue.filter(column.filter.name)
var args = [].concat(value, column.filter.args)
return filter.apply(this, args)
}
return value
},
calculateSum: function (column) {
var sum = this.data.reduce(function (a, b) {
return a + b[column.key]
}, 0)
if (column.hasOwnProperty('filter')) {
var filter = Vue.filter(column.filter.name)
return filter.apply(this, [sum])
}
return sum
}
},
events: {
'refreshData': function () {
this.selectedRow = null
this.selectedCol = null
this.resetSelection()
this.fetchData()
},
'paginationNext': function () {
console.time('Vue')
}
},
watch: {
'selectAll': function (value) {
this.selectedRows = value ? [].concat(this.data) : []
},
'searchQuery': function (value) {
this.paginationData.current_page = 1
this.fetchData()
},
'filterQuery': function (value) {
this.paginationData.current_page = 1
this.fetchData()
}
},
directives: {
'cell-focus': function (value) {
if (!value) {
return
}
var el = this.el
Vue.nextTick(function () {
el.focus()
})
}
}
}
</script>
<style lang="stylus">
.overflow-visible
overflow: visible !important;
.datagrid-toggle-column
width: 58px
.table thead tr th
padding-top: 10px
padding-bottom: 10px
&.datagrid-toggle-column .checkbox
height: 18px
width: 19px
.editCell
display: none
background-color: #f5f5f5
border: 1px solid #eee
width: 100%
height: 33px
.editing
.cellContent
display: none
.editCell
display: block
padding: 5px 8px
.ui.segment
position: relative
.ui.segment:first-child
margin-top: 0em
.ui.segment:last-child
margin-bottom: 0em
.ui.loading.segment
position: relative
cursor: default
point-events: none
text-shadow: none !important
color: transparent !important
-webkit-transition: all 0s linear
transition: all 0s linear
.ui.loading.segment:before
position: absolute
content: ''
top: 0%
left: 0%
background: rgba(255, 255, 255, 0.8)
width: 100%
height: 100%
border-radius: 0.28571429rem
z-index: 100
.ui.loading.segment:after
position: absolute
content: ''
top: 50%
left: 50%
margin: -1.5em 0em 0em -1.5em
width: 3em
height: 3em
-webkit-animation: segment-spin 0.6s linear
animation: segment-spin 0.6s linear
-webkit-animation-iteration-count: infinite
animation-iteration-count: infinite
border-radius: 500rem
border-color: #767676 rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1)
border-style: solid
border-width: 0.2em
box-shadow: 0px 0px 0px 1px transparent
visibility: visible
z-index: 101
@-webkit-keyframes segment-spin
from
-webkit-transform: rotate(0deg)
transform: rotate(0deg)
to
-webkit-transform: rotate(360deg)
transform: rotate(360deg)
@keyframes segment-spin
from
-webkit-transform: rotate(0deg)
transform: rotate(0deg)
to
-webkit-transform: rotate(360deg)
transform: rotate(360deg)
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment