datatable.directory codebase https://datatable.directory/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
datatable.directory/resources/assets/js/components/ColumnEditor.vue

315 lines
8.5 KiB

<!--
Complex animated column editor for the table edit page
-->
<template>
<div>
<input type="hidden" :name="name" :value="JSON.stringify(columns)" v-if="newTable">
<table class="editor-table">
<thead>
<tr>
<th v-if="sortable"></th>
<th>Name</th>
<th>Type</th>
<th>Title</th>
<th v-if="!newTable"></th>
<th></th>
<th></th>
</tr>
</thead>
<transition-group name="col-list" tag="tbody" ref="col-list">
<tr v-for="(col, i) in columns" :key="col.id" :ref="'col' + i" :class="{dragging: col._dragging}">
<td class="btn-group" v-if="sortable">
<button type="button" class="btn btn-outline-secondary drag-btn" @mousedown="beginDrag(i, $event)">
<v-icon class="fa-bars" alt="Drag" />
</button><!--
--><button type="button" :class="['btn', 'btn-outline-secondary', {disabled: i==0}]" v-if="manualSort"
@click.prevent="move(i, -1)">
<v-icon class="fa-chevron-up" alt="Move Up" />
</button><!--
--><button type="button" :class="['btn', 'btn-outline-secondary', {disabled: i == (columns.length-1)}]" v-if="manualSort"
@click.prevent="move(i, 1)">
<v-icon class="fa-chevron-down" alt="Move Down" />
</button>
</td>
<td>
<input v-model="col.name"
@input="markColNeedSave(i)"
class="form-control"
type="text"
style="width: 140px">
</td>
<td>
<select v-model="col.type"
@input="markColNeedSave(i)"
class="form-control custom-select"
style="width: 110px">
<option v-for="t in colTypes" :value="t">{{t}}</option>
</select>
</td>
<td>
<input v-model="col.title"
@input="markColNeedSave(i)"
class="form-control"
type="text"
style="width: 170px">
</td>
<td v-if="!newTable">
<button type="button" :class="['btn', col._dirty ? 'btn-info' : 'btn-outline-secondary']"
@click.prevent="saveCol(i)">
<v-icon class="fa-save" alt="Save" />
</button>
</td>
<td>
<button type="button" :class="delBtnClass(col)"
@click.prevent="toggleColDelete(i)">
<v-icon :class="col._remove ? 'fa-undo' : 'fa-trash-o'"
:alt="col._remove ? 'Undo Remove' : 'Remove'" />
</button>
</td>
<td>
<button type="button" :class="['btn', 'btn-outline-secondary']"
v-if="i === columns.length - 1"
@click.prevent="addCol()">
<v-icon class="fa-plus" alt="Add Column" />
</button>
</td>
</tr>
</transition-group>
</table>
</div>
</template>
<style lang="scss" scoped>
@import "base";
table {
border-collapse: collapse;
}
td, th {
@include pr(1);
6 years ago
@include py(1);
}
tr.dragging {
position: relative;
z-index: 1;
background-color: $body-bg;
}
tr.dragging .drag-btn {
// fake hover
background-color: $secondary;
color: color-yiq($secondary);
}
.col-list-enter-active {
transition: all .3s cubic-bezier(.2, .3, 0, 1);
}
.col-list-enter {
opacity: 0;
transform: translateY(100%);
}
.col-list-leave {
// approximate position of delete button
clip-path: circle(calc(100% + 5em) at calc(100% - 4em) 50%);
}
.col-list-leave-active .delete-btn {
animation: col-list-leave-delete-btn .3s forwards;
}
.col-list-leave-active {
transition: all .3s cubic-bezier(.4, .1, .6, .9);
}
.col-list-leave-to {
clip-path: circle(0 at calc(100% - 4em) 50%);
}
@keyframes col-list-leave-delete-btn {
0% {
transition-timing-function: cubic-bezier(.2, .4, .6, .9);
}
50% {
transition-timing-function: cubic-bezier(.5, 0, .8, .5);
transform: scale(1.1);
opacity: 1;
}
100% {
transform: scale(0);
opacity: 0;
}
}
.col-list-move {
transition: transform .3s cubic-bezier(.2, .3, 0, 1);
}
</style>
<script>
import { query } from './table-editor-utils'
export default {
props: {
route: { type: String, default: null },
sortable: { type: Boolean, default: true },
manualSort: { type: Boolean, default: false },
newTable: { type: Boolean, default: false },
name: { type: String, required: true },
xColumns: { type: Array, required: true },
},
data: function () {
return {
columns: this.xColumns,
colTypes: ['string', 'int', 'float', 'bool'],
}
},
methods: {
6 years ago
/** Send a query to the server */
query (data, sucfn, erfn) {
query(this.route, data, sucfn, erfn)
},
6 years ago
/** Add a column at the end */
addCol () {
if (this.newTable) {
this.columns.push({
id: Math.random().toString(),
name: '',
type: 'string',
title: '',
})
} else {
this.query({
action: 'col.add',
}, (resp) => {
this.columns.push(resp.data)
})
}
},
/** Move a column (dir is positive or negative) */
6 years ago
move (pos1, dir) {
let pos2 = pos1 + dir
// clamp
if (pos2 < 0) {
pos2 = 0
} else if (pos2 >= this.columns.length) {
pos2 = this.columns.length - 1
6 years ago
}
let columns = this.columns
let col1 = columns.splice(pos1, 1)[0] // unwrap returned 1-element array
columns.splice(pos2, 0, col1)
this.columns = columns
},
6 years ago
/** Indicate a column is dirty and needs saving */
markColNeedSave(i) {
this.$set(this.columns[i], '_dirty', true)
},
6 years ago
/** User started dragging a column */
beginDrag (i, evt) {
const column = this.columns[i]
column._dragging = true
this.$set(this.columns, i, column) // notify vue
let currentIndex = i
const dragMoveListener = e => {
let cursorIndex = 0
// find cursor index by going through the list and adding the li
// heights (can’t use their positions because they may be animating at
// that moment)
let accumY = this.$refs['col-list'].$el.getBoundingClientRect().top
for (let i = 0; i < this.columns.length; i++) {
if (e.clientY > accumY) {
cursorIndex = i
}
accumY += $(this.$refs['col' + i]).outerHeight(true)
}
if (cursorIndex !== currentIndex) {
this.move(currentIndex, cursorIndex - currentIndex)
currentIndex = cursorIndex
}
}
const dragEndListener = e => {
column._dragging = false
this.$set(this.columns, currentIndex, column) // notify vue
$(window).off('mousemove', dragMoveListener)
$(window).off('mouseup', dragEndListener)
}
$(window).on('mousemove', dragMoveListener)
$(window).on('mouseup', dragEndListener)
},
6 years ago
/** Compute classes for the column's delete button */
delBtnClass(col) {
return [
'btn',
'btn-outline-danger',
'delete-btn',
{'active': col._remove},
{disabled: this.newTable && this.columns.length === 1}
]
},
6 years ago
/** Save a column */
saveCol(n) {
let col = this.columns[n]
if (_.isUndefined(col)) return
this.query({
action: 'col.update',
data: col
}, (resp) => {
this.$set(this.columns, n, resp.data)
}, (er) => {
if (!_.isUndefined(er.errors)) {
this.$set(this.columns[n], '_errors', er.errors)
}
})
},
6 years ago
/** Delete / undelete a column; New columns vanish. */
toggleColDelete(n) {
let col = this.columns[n]
if (_.isUndefined(col)) return
if (this.newTable) {
// hard delete
this.columns.splice(n, 1)
} else {
let remove = !col._remove
this.query({
action: remove ? 'col.remove' : 'col.restore',
id: col.id
}, (resp) => {
// if response is null, this was a New col
// and it was discarded without a way back - hard drop
if (_.isEmpty(resp.data)) {
this.$delete(this.col, n)
}
else {
this.$set(this.columns, n, resp.data)
}
})
}
},
}
}
</script>