JSFiddle - React, Tailwind, and code Playground
by skirtle
HTML
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<template id="vt-gridview">
<table v-if="rows" ref="table" :class="['v-gridView', {rowLines: rowLines}]">
<tr v-if="rows.length === 0">
<td>
<div style="padding:4px;" class="noRec">{{empty}}</div>
</td>
</tr>
<template v-else>
<tr v-if="showHeader">
<th v-if="colShow(col)" v-for="col in cols" :style="headStyle(col)" :title="col.hTitle" @click="doSort(col)">
<slot v-if="col.hSlot" :name="col.hSlot" :col="col"></slot>
<template v-else>
{{col.name}}<span v-if="sort" v-show="sort.exp==col.sort" style="margin-left:3px;">{{sortGliph}}</span>
</template>
</th>
</tr>
<v-gridview-tr v-for="(row, idx) in rows" :key="row[rowKeyProp]" :row="row" :idx="idx" :cols="cols" :col-key-prop="colKeyProp" :cols-hidden="colsHidden" :alt-rows="altRows" :sel-row="selRow" @row-selected="rowSelected">
<template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
<slot :name="name" v-bind="data"></slot>
</template>
</v-gridview-tr>
</template>
</table>
</template>
<template id="vt-gridview-tr">
<tr :class="rowClass" :style="rowStyle" @click="rowClick">
<td v-if="colShow(col)" v-for="col in cols" :key="col[colKeyProp]" :class="cellClass(col)" :style="cellStyle(col)" :title="row[col.bTitle]" @click="cellClick(col)">
<slot v-if="col.bSlot" :name="col.bSlot" :row-idx="idx" :row="row" :col="col"></slot>
<div v-else-if="col.html" v-html="formatField(col)"></div>
<template v-else>
{{formatField(col)}}
</template>
</td>
</tr>
</template>
<div id="vmPage" class="flexVert" style="height:100%">
<v-gridview v-bind="trans" :sort.sync="trans.sort" @update:sel-row="updateSelRow" style="width:100%;">
<template v-slot:trans="{row}">
{{ row.TransID }} <span v-show="row.dirty" style="color:var(--error-color);">✽</span>
</template>
...
CSS
:root {
--surf-color-hs: 200, 15%;
--on-color-hsl: 0, 0%, 0%;
--error-color-hs: 360, 100%;
--sel-color-h: 40;
--scrbar-bk-color: hsl(var(--surf-color-hs), 94%);
--scrbar-sl-color: hsl(var(--surf-color-hs), 70%);
--surf0-color: hsl(var(--surf-color-hs), 92%);
--surf1-color: hsl(var(--surf-color-hs), 96%);
--surf2-color: hsl(var(--surf-color-hs), 98%);
--surf3-color: hsl(var(--surf-color-hs), 100%);
--grid-rowhf-color: hsl(var(--surf-color-hs), 92%);
--grid-row0-color: hsl(var(--surf-color-hs), 100%);
--grid-row1-color: hsl(var(--surf-color-hs), 96%);
--grid-line-color: hsl(var(--surf-color-hs), 83%);
--cont-bk-color: hsl(var(--surf-color-hs), 100%);
--cont-bd-color: hsl(var(--surf-color-hs), 78%);
--input-bk-color: hsl(var(--surf-color-hs), 100%);
--input-bd0-color: hsl(var(--surf-color-hs), 85%);
--input-bd1-color: hsl(var(--surf-color-hs), 70%);
--icon-color-filter: opacity(60%);
--sel-thick-color: hsl(var(--sel-color-h), 70%, 80%);
--sel-thin-color: hsl(var(--sel-color-h), 70%, 80%);
--sel-webTWAIN-color: 0x0A74A9;
--error-color: hsl(var(--error-color-hs), 40%);
--link-color0: #004078;
--link-color1: #006ecf;
--font-fam-main: Verdana, Geneva, sans-serif;
--font-fam-label: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
--em-high: .87;
--em-med: .6;
--em-dis: .38;
}
.v-gridView {
line-height: 1.1;
display: table;
border-collapse: collapse;
border-bottom: solid 1px hsla(var(--on-color-hsl), .08);
}
.v-gridView th {
display: table-cell;
border-collapse: collapse;
padding: 4px 3px 3px 3px;
background-color: var(--grid-rowhf-color);
border-bottom: 1px solid var(--grid-line-color);
font-family: var(--font-fam-label);
font-weight: normal;
font-size: 12px;
color:...
JavaScript
Vue.component('v-gridview', {
template: '#vt-gridview',
props: {
rowKeyProp: {
type: String,
default: 'ID'
},
colKeyProp: {
type: String,
default: 'field'
},
showHeader: {
default: true
},
cols: {
type: Array,
required: true
},
colsHidden: Array,
empty: String,
altRows: {
default: true
},
rowLines: {
default: false
},
rows: Array,
rowClassFn: Function,
cellStyleFn: Function,
sort: Object,
selRow: Object
},
created() {
},
updated () {
console.log('updated: parent')
},
computed: {
sortGliph() {
return this.sort.ord == 'ASC' ? '▲' : '▼';
}
},
methods: {
colShow(col) {
return this.colsHidden ? !this.colsHidden.includes(col.field) : true;
},
colSortable(col) {
return this.sort && col.sort
},
doSort(col) {
if (!this.colSortable(col)) return;
const sort = {
exp: col.sort,
ord: this.sort.exp != col.sort ? 'ASC' : this.sort.ord == 'ASC' ? 'DESC' : 'ASC'
}
this.$emit('update:sort', sort); // sync to parent
this.$emit('sort-chng', sort);
},
headStyle(col) {
return [col.hStyle, {
cursor: this.colSortable(col) ? 'pointer' : ''
}];
},
rowSelected(row) {
this.$emit('update:sel-row', row);
console.log('%crowSelected', 'color: red');
}
}
});
Vue.component('v-gridview-tr', {
template: '#vt-gridview-tr',
props: {
row: Object,
idx: Number,
colKeyProp: String,
cols: Array,
colsHidden: Array,
altRows: Boolean,
rowClassFn: Function,
cellStyleFn: Function,
selRow: Object
},
data() {
return {
selected: false
};
},
watch: {
'selRow.value': {
immediate: true,
handler(val) {
this.selected = val === this.row;
}
}
},
updated () {
console.log('updated: ' + this.idx)
},
computed: {
...