CSV import, also from file, and more UX improvements

This commit is contained in:
2018-08-10 20:19:16 +02:00
parent 1e183f5059
commit a3df7a724c
18 changed files with 371 additions and 155 deletions
@@ -5,7 +5,12 @@ 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="[{'table': !newTable}, {'new-table': newTable}, {'mt-3': !newTable}, 'table-narrow', 'table-sm', 'table-fixed', 'td-va-middle']">
<table :class="[
{'table': !newTable},
{'new-table': newTable},
{'mt-3': !newTable},
'table-narrow', 'table-sm', 'table-fixed', 'td-va-middle'
]">
<thead>
<tr>
<th v-if="sortable"></th>
@@ -16,7 +21,12 @@ Complex animated column editor for the table edit page
</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, 'text-success': col._new}">
<tr v-for="(col, i) in columns" :key="col.id" :ref="`col${i}`"
:class="{
dragging: col._dragging,
'text-success': col._new,
remove: col._remove
}">
<td v-if="sortable">
<span class="btn-group">
<button type="button" class="btn btn-outline-secondary drag-btn"
@@ -74,17 +84,17 @@ Complex animated column editor for the table edit page
<td class="text-nowrap"><!--
Save button
--><a href="" :class="['mr-1','btn','btn-outline-secondary',{'disabled': col._remove}]"
--><a href="" :class="['mr-1', 'btn', 'btn-outline-secondary', {'disabled': col._remove}]"
v-if="!newTable"
@click.prevent="toggleColEditing(col)">
<v-icon :class="col._editing ? 'fa-save' : 'fa-pencil'"
:alt="col._editing ? 'Save' : 'Edit'" />
<v-icon v-if="col._editing" class="fa-save" alt="Save" />
<v-icon v-else class="fa-pencil" alt="Edit" />
</a><!--
Delete button
--><button type="button" :class="delBtnClass(col)"
@click.prevent="toggleColDelete(col)">
<v-icon :class="col._remove ? 'fa-undo' : 'fa-trash-o'"
:alt="col._remove ? 'Undo Remove' : 'Remove'" />
<v-icon v-if="col._remove" class="fa-undo" alt="Undo Remove" />
<v-icon v-else class="fa-trash-o" alt="Remove" />
</button><!--
Add button
--><button type="button" :class="['x-add-btn', 'btn', 'btn-outline-secondary']"
@@ -387,7 +397,7 @@ export default {
if (cell == 'title') w = '14'
return {width: `${w}rem`};
}
},
}
}
</script>
+81 -33
View File
@@ -11,7 +11,7 @@ Rows are identified by row._id, columns by col.id
<template>
<div>
<div class="col-md-12 d-flex mt-3" v-if="newRows || hasPages">
<div class="col-md-12 d-flex mt-3">
<div v-if="hasPages" v-html="pager"></div>
<div class="flex-grow-1"></div>
@@ -21,52 +21,68 @@ Rows are identified by row._id, columns by col.id
<input type="hidden" name="action" value="rows.add">
<input type="hidden" name="redirect" :value="pageUrl">
<input name="count" id="newrow-count" type="number"
min=1 step=1 value=1 class="form-control mr-1" style="width: 10em">
min=1 step=1 value=1 class="form-control mr-1" style="width: 8em">
<button class="btn btn-outline-success">Add Rows</button>
</form>
<a href="" class="btn btn-outline-success mr-2" v-if="newRows">
<a :href="loadCsvUrl" class="btn btn-outline-success mr-2" v-if="newRows && loadCsvUrl">
<v-icon class="fa-file-excel-o fa-pr" alt="CSV"></v-icon><!--
-->CSV
</a>
<form :action="route" method="POST" v-if="newRows" class="form-inline mr-2">
<input type="hidden" name="_token" :value="csrf">
<input type="hidden" name="action" value="rows.remove-empty-new">
<input type="hidden" name="action" value="row.remove-empty-new">
<input type="hidden" name="redirect" :value="pageUrl">
<button class="btn btn-outline-danger">Remove Empty</button>
</form>
<button @click="saveAllChanges" type="button" v-if="newRows"
<button @click="saveAllChanges" type="button"
:class="['btn', this.dirtyRows ? 'btn-info' : 'btn-outline-secondary']">
Save Rows
</button>
</div>
<div class="col-md-12 mt-3">
<table class="table table-hover table-sm table-fixed td-va-middle">
<table :class="[
'table', 'table-sm', 'table-fixed', 'td-va-middle',
{'new-rows': newRows}
]">
<thead>
<tr>
<th style="width:6rem"></th>
<th v-for="col in columns" :class="colClasses(col)" :title="col.name">{{col.title}}</th>
<th style="width:2rem"><!-- revert icon --></th>
</tr>
</thead>
<tbody>
<tr v-for="row in rows" :style="rowStyle(row)">
<tr v-for="row in rows" :class="row._remove ? 'remove' : ''">
<td class="text-nowrap">
<button :class="['mr-1', 'btn', 'btn-outline-danger', {'active': row._remove}]"
<button :class="[
'mr-1', 'btn', 'btn-outline-danger',
{'active': row._remove}
]"
@click.prevent="toggleRowDelete(row._id)">
<v-icon :class="row._remove ? 'fa-undo' : 'fa-trash-o'"
:alt="row._remove ? 'Undo Remove' : 'Remove'" />
</button><!--
--><button v-if="newRows" :class="['mr-1', 'btn', isRowChanged(row) ? 'btn-info' : 'btn-outline-secondary']"
--><button v-if="newRows"
:class="[
'mr-1', 'btn',
isRowChanged(row) ? 'btn-info' : 'btn-outline-secondary'
]"
@click.prevent="submitRowChange(row)">
<v-icon class="fa-save" alt="Save" />
</button><!--
--><button v-else :class="['btn', 'btn-outline-secondary', {'disabled': row._remove}]"
--><button v-else
:class="[
'btn',
isRowChanged(row) ? 'btn-info' : 'btn-outline-secondary',
{'disabled': row._remove}
]"
@click.prevent="toggleRowEditing(row._id)">
<v-icon :class="row._editing ? 'fa-save' : 'fa-pencil'"
:alt="row._editing ? 'Save' : 'Edit'" />
<v-icon v-if="row._editing" class="fa-save" alt="Save" />
<v-icon v-else class="fa-pencil" alt="Edit" />
</button>
</td>
@@ -90,6 +106,13 @@ Rows are identified by row._id, columns by col.id
alt="Revert Change" />
</td>
</template>
<td style="text-align: center;">
<v-icon @click="discardEdit(row)"
v-if="isRowChanged(row) && (row._editing || newRows)"
class="fa-undo text-danger pointer"
alt="Revert Change" />
</td>
</tr>
</tbody>
</table>
@@ -107,6 +130,10 @@ Rows are identified by row._id, columns by col.id
th {
border-top: 0 none;
}
table.new-rows td {
border: 0 none;
}
</style>
<script>
@@ -128,6 +155,8 @@ Rows are identified by row._id, columns by col.id
csrf: String,
/** Full URL of the current page */
pageUrl: String,
/** URL to import CSV */
loadCsvUrl: String,
/** Indicate this is the Add Rows page */
newRows: {
type: Boolean,
@@ -144,12 +173,14 @@ Rows are identified by row._id, columns by col.id
},
watch: {
dirtyRows: function (value) {
console.log(`Dirty Rows ${value}`)
if (value > 0) {
$(window).on('beforeunload', function () {
return 'Some row changes are not saved.';
});
return 'Some row changes are not saved.'
})
} else {
$(window).off('beforeunload');
$(window).off('beforeunload')
}
}
},
@@ -222,24 +253,24 @@ Rows are identified by row._id, columns by col.id
}
},
/** Compute style for a row */
rowStyle (row) {
return {
opacity: row._remove ? .8 : 1,
backgroundColor:
row._remove ? '#FFC4CC' :
'transparent'
}
},
/** Test if a value cell is changed */
isChanged (row, colId) {
return row._changed && row._changed.indexOf(colId) > -1
},
/** Test if any field in the row is changed */
isRowChanged(row) {
return row._changed && row._changed.length
isRowChanged (row, cached = false) {
if (this.newRows) {
return row._changed && row._changed.length
} else {
if (cached) return row._dirty
// check against loadvals array
for (const [key, value] of Object.entries(row._loadvals)) {
// changed if differs from orig value and also from previous value from revision
if (row[key] != value && row[key] != row._orig[key]) return true
}
return false
}
},
/** Revert a value cell */
@@ -247,10 +278,9 @@ Rows are identified by row._id, columns by col.id
this.submitRowChange(_.merge({}, row, {[colId]: row._orig[colId]}))
},
markRowDirty(row) {
if (!this.newRows) return
let wasDirty = this.isRowChanged(row)
/** Mark row as dirty and needing a save */
markRowDirty (row) {
let wasDirty = this.isRowChanged(row, true)
let changes = []
_.each(row._orig, (v, k) => {
@@ -259,14 +289,17 @@ Rows are identified by row._id, columns by col.id
}
})
this.rows[row._id]._changed = changes;
this.rows[row._id]._changed = changes
let isDirty = this.isRowChanged(row)
if (wasDirty && !isDirty) this.dirtyRows--
else if (!wasDirty && isDirty) this.dirtyRows++
row._dirty = isDirty
},
saveAllChanges() {
/** Save all changed rows */
saveAllChanges () {
let toChange = _.filter(this.rows, (r) => {
return this.isRowChanged(r)
})
@@ -286,6 +319,21 @@ Rows are identified by row._id, columns by col.id
// this.$set(this.rows[row._id], '_errors', er.errors)
// }
})
},
discardEdit (row) {
let wasDirty = this.isRowChanged(row)
_.each(this.newRows ? row._orig : row._loadvals, (val, id) => {
row[id] = val
})
row._editing = false
row._dirty = false
if (this.newRows) row._changed = []
this.$set(this.rows, row._id, row)
if (wasDirty) this.dirtyRows--
}
}
}
+5
View File
@@ -1 +1,6 @@
@import "../../sass/bootstrap-base";
tr.remove, tr.remove:hover {
//opacity: .8;
background-color: #FFC4CC;
}