Implemented Cell Editing for Table
parent
dc0adff628
commit
c759ca869a
|
@ -88,7 +88,7 @@ export default {
|
|||
if (!this.documentEditListener) {
|
||||
this.documentEditListener = (event) => {
|
||||
if (this.isOutsideClicked(event)) {
|
||||
this.switchCellToViewMode();
|
||||
this.completeEdit(event, 'outside');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -108,27 +108,39 @@ export default {
|
|||
isOutsideClicked(event) {
|
||||
return !this.$el.contains(event.target) && !this.$el.isSameNode(event.target);
|
||||
},
|
||||
onClick() {
|
||||
onClick(event) {
|
||||
if (this.isEditable() && !this.editing) {
|
||||
this.editing = true;
|
||||
|
||||
this.bindDocumentEditListener();
|
||||
this.$emit('edit-init', {originalEvent: event, data: this.rowData, field: this.column.field, index: this.index});
|
||||
}
|
||||
},
|
||||
completeEdit(event, type) {
|
||||
let editEvent = {originalEvent: event, data: this.rowData, field: this.column.field, index: this.index, type: type, preventDefault: () => event.preventDefault()};
|
||||
this.$emit('edit-complete', editEvent);
|
||||
|
||||
if (!event.defaultPrevented) {
|
||||
this.switchCellToViewMode();
|
||||
}
|
||||
},
|
||||
onKeyDown(event) {
|
||||
switch (event.which) {
|
||||
case 13:
|
||||
this.completeEdit(event, 'enter');
|
||||
break;
|
||||
|
||||
case 27:
|
||||
this.switchCellToViewMode();
|
||||
this.$emit('edit-cancel', {originalEvent: event, data: this.rowData, field: this.column.field, index: this.index});
|
||||
break;
|
||||
|
||||
case 9:
|
||||
this.completeEdit(event, 'tab');
|
||||
|
||||
if (event.shiftKey)
|
||||
this.moveToPreviousCell(event);
|
||||
else
|
||||
this.moveToNextCell(event);
|
||||
|
||||
this.switchCellToViewMode();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -202,6 +214,9 @@ export default {
|
|||
else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
isEditingCellValid() {
|
||||
return (DomHandler.find(this.$el, '.p-invalid').length === 0);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -44,6 +44,7 @@ export declare class DataTable extends Vue {
|
|||
expandedRowGroups?: any[];
|
||||
stateStorage?: string;
|
||||
stateKey?: string;
|
||||
editMode?: string;
|
||||
$emit(eventName: 'page', event: Event): this;
|
||||
$emit(eventName: 'sort', event: Event): this;
|
||||
$emit(eventName: 'filter', event: Event): this;
|
||||
|
@ -56,6 +57,9 @@ export declare class DataTable extends Vue {
|
|||
$emit(eventName: 'row-collapse', event: Event): this;
|
||||
$emit(eventName: 'rowgroup-expand', event: Event): this;
|
||||
$emit(eventName: 'rowgroup-collapse', event: Event): this;
|
||||
$emit(eventName: 'edit-init', event: Event): this;
|
||||
$emit(eventName: 'edit-complete', event: Event): this;
|
||||
$emit(eventName: 'edit-cancel', event: Event): this;
|
||||
$slots: {
|
||||
header: VNode[];
|
||||
paginatorLeft: VNode[];
|
||||
|
|
|
@ -71,7 +71,8 @@
|
|||
<DTBodyCell v-if="shouldRenderBodyCell(dataToRender, col, index)" :key="col.columnKey||col.field||i" :rowData="rowData" :column="col" :index="index" :selected="isSelected(rowData)"
|
||||
:rowTogglerIcon="col.expander ? rowTogglerIcon(rowData): null" @row-toggle="toggleRow"
|
||||
@radio-change="toggleRowWithRadio" @checkbox-change="toggleRowWithCheckbox"
|
||||
:rowspan="rowGroupMode === 'rowspan' ? calculateRowGroupSize(dataToRender, col, index) : null" />
|
||||
:rowspan="rowGroupMode === 'rowspan' ? calculateRowGroupSize(dataToRender, col, index) : null"
|
||||
@edit-init="onEditInit" @edit-complete="onEditComplete" @edit-cancel="onEditCancel" />
|
||||
</template>
|
||||
</tr>
|
||||
<tr class="p-datatable-row-expansion" v-if="expandedRows && isRowExpanded(rowData)" :key="getRowKey(rowData, index) + '_expansion'">
|
||||
|
@ -303,6 +304,10 @@ export default {
|
|||
stateKey: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
editMode: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -1557,6 +1562,15 @@ export default {
|
|||
let headers = DomHandler.find(this.$refs.table, '.p-datatable-thead > tr > th');
|
||||
headers.forEach((header, index) => header.style.width = widths[index] + 'px');
|
||||
}
|
||||
},
|
||||
onEditInit(event) {
|
||||
this.$emit('edit-init', event);
|
||||
},
|
||||
onEditComplete(event) {
|
||||
this.$emit('edit-complete', event);
|
||||
},
|
||||
onEditCancel(event) {
|
||||
this.$emit('edit-cancel', event);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -10,8 +10,9 @@
|
|||
</div>
|
||||
|
||||
<div class="content-section implementation">
|
||||
<h3>Cell Editing</h3>
|
||||
<DataTable :value="cars">
|
||||
<h3>Basic Cell Editing</h3>
|
||||
<p>Simple editors with v-model.</p>
|
||||
<DataTable :value="cars1" editMode="cell">
|
||||
<Column field="vin" header="Vin">
|
||||
<template #editor="slotProps">
|
||||
<InputText v-model="slotProps.data[slotProps.column.field]" />
|
||||
|
@ -19,7 +20,7 @@
|
|||
</Column>
|
||||
<Column field="year" header="Year">
|
||||
<template #editor="slotProps">
|
||||
<InputText v-model="slotProps.data['year']" />
|
||||
<InputText v-model="slotProps.data[slotProps.column.field]" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="brand" header="Brand">
|
||||
|
@ -36,23 +37,128 @@
|
|||
</Column>
|
||||
<Column field="color" header="Color">
|
||||
<template #editor="slotProps">
|
||||
<InputText v-model="slotProps.data['color']" />
|
||||
<InputText v-model="slotProps.data[slotProps.column.field]" />
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
|
||||
<h3>Advanced Cell Editing</h3>
|
||||
<p>Advanced editors with validations and ability to revert values with escape key.</p>
|
||||
<DataTable :value="cars2" editMode="cell" @edit-init="onEditInit" @edit-complete="onEditComplete" @edit-cancel="onEditCancel">
|
||||
<Column field="vin" header="Vin">
|
||||
<template #editor="slotProps">
|
||||
<InputText :value="slotProps.data[slotProps.column.field]" @input="onEdit($event, slotProps)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="year" header="Year">
|
||||
<template #editor="slotProps">
|
||||
<InputText :value="slotProps.data[slotProps.column.field]" @input="onEdit($event, slotProps)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="brand" header="Brand">
|
||||
<template #editor="slotProps">
|
||||
<Dropdown v-model="slotProps.data['brand']" :options="brands" optionLabel="brand" optionValue="value" placeholder="Select a Brand">
|
||||
<template #option="optionProps">
|
||||
<div class="p-dropdown-car-option">
|
||||
<img :alt="optionProps.option.brand" :src="'demo/images/car/' + optionProps.option.brand + '.png'" />
|
||||
<span>{{optionProps.option.brand}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="color" header="Color">
|
||||
<template #editor="slotProps">
|
||||
<InputText :value="slotProps.data[slotProps.column.field]" @input="onEdit($event, slotProps)" />
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<div class="content-section documentation">
|
||||
<TabView>
|
||||
<TabPanel header="Source">
|
||||
<CodeHighlight>
|
||||
<template v-pre>
|
||||
<h3>Basic Cell Editing</h3>
|
||||
<p>Simple editors with v-model.</p>
|
||||
<DataTable :value="cars1" editMode="cell">
|
||||
<Column field="vin" header="Vin">
|
||||
<template #editor="slotProps">
|
||||
<InputText v-model="slotProps.data[slotProps.column.field]" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="year" header="Year">
|
||||
<template #editor="slotProps">
|
||||
<InputText v-model="slotProps.data[slotProps.column.field]" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="brand" header="Brand">
|
||||
<template #editor="slotProps">
|
||||
<Dropdown v-model="slotProps.data['brand']" :options="brands" optionLabel="brand" optionValue="value" placeholder="Select a Brand">
|
||||
<template #option="optionProps">
|
||||
<div class="p-dropdown-car-option">
|
||||
<img :alt="optionProps.option.brand" :src="'demo/images/car/' + optionProps.option.brand + '.png'" />
|
||||
<span>{{optionProps.option.brand}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="color" header="Color">
|
||||
<template #editor="slotProps">
|
||||
<InputText v-model="slotProps.data[slotProps.column.field]" />
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
|
||||
<h3>Advanced Cell Editing</h3>
|
||||
<p>Advanced editors with validations and ability to revert values with escape key.</p>
|
||||
<DataTable :value="cars2" editMode="cell" @edit-init="onEditInit" @edit-complete="onEditComplete" @edit-cancel="onEditCancel">
|
||||
<Column field="vin" header="Vin">
|
||||
<template #editor="slotProps">
|
||||
<InputText :value="slotProps.data[slotProps.column.field]" @input="onEdit($event, slotProps)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="year" header="Year">
|
||||
<template #editor="slotProps">
|
||||
<InputText :value="slotProps.data[slotProps.column.field]" @input="onEdit($event, slotProps)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="brand" header="Brand">
|
||||
<template #editor="slotProps">
|
||||
<Dropdown v-model="slotProps.data['brand']" :options="brands" optionLabel="brand" optionValue="value" placeholder="Select a Brand">
|
||||
<template #option="optionProps">
|
||||
<div class="p-dropdown-car-option">
|
||||
<img :alt="optionProps.option.brand" :src="'demo/images/car/' + optionProps.option.brand + '.png'" />
|
||||
<span>{{optionProps.option.brand}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="color" header="Color">
|
||||
<template #editor="slotProps">
|
||||
<InputText :value="slotProps.data[slotProps.column.field]" @input="onEdit($event, slotProps)" />
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</template>
|
||||
</CodeHighlight>
|
||||
|
||||
<CodeHighlight lang="javascript">
|
||||
import CarService from '../../service/CarService';
|
||||
import DataTableSubMenu from './DataTableSubMenu';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
columns: null,
|
||||
cars: null,
|
||||
cars1: null,
|
||||
cars2: null,
|
||||
editingCar: null,
|
||||
editingCarIndex: null,
|
||||
originalCar: null,
|
||||
brands: [
|
||||
{brand: 'Audi', value: 'Audi'},
|
||||
{brand: 'BMW', value: 'BMW'},
|
||||
|
@ -69,16 +175,157 @@ export default {
|
|||
carService: null,
|
||||
created() {
|
||||
this.carService = new CarService();
|
||||
},
|
||||
methods: {
|
||||
onEditInit(event) {
|
||||
this.editingCarIndex = event.index;
|
||||
this.editingCar = {...event.data}; //update on input
|
||||
this.originalCar = {...event.data}; //revert with escape key
|
||||
},
|
||||
onEditComplete(event) {
|
||||
switch (event.field) {
|
||||
case 'year':
|
||||
if (this.isPositiveInteger(this.editingCar.year)) {
|
||||
Vue.set(this.cars2, this.editingCarIndex, this.editingCar);
|
||||
}
|
||||
else {
|
||||
this.$toast.add({severity:'error', summary: 'Validation Failed', detail:'Year must be a number', life: 3000});
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
|
||||
this.columns = [
|
||||
{field: 'vin', header: 'Vin'},
|
||||
{field: 'year', header: 'Year'},
|
||||
{field: 'brand', header: 'Brand'},
|
||||
{field: 'color', header: 'Color'}
|
||||
];
|
||||
default:
|
||||
if (this.editingCar[event.field].trim().length > 0) {
|
||||
Vue.set(this.cars2, this.editingCarIndex, this.editingCar);
|
||||
}
|
||||
else {
|
||||
this.$toast.add({severity:'error', summary: 'Validation Failed', detail: event.field + ' is required', life: 3000});
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
clearEditorState() {
|
||||
this.editingCar = null;
|
||||
this.originalCar = null;
|
||||
},
|
||||
onEdit(newValue, props) {
|
||||
this.editingCar[props.column.field] = newValue;
|
||||
},
|
||||
onEditCancel(event) {
|
||||
Vue.set(this.cars2, event.index, this.originalCar);
|
||||
this.editingCar = null;
|
||||
},
|
||||
isPositiveInteger(val) {
|
||||
let str = String(val);
|
||||
str = str.trim();
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
str = str.replace(/^0+/, "") || "0";
|
||||
var n = Math.floor(Number(str));
|
||||
return n !== Infinity && String(n) === str && n >= 0;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.carService.getCarsSmall().then(data => this.cars = data);
|
||||
this.carService.getCarsSmall().then(data => this.cars1 = data);
|
||||
this.carService.getCarsSmall().then(data => this.cars2 = data);
|
||||
},
|
||||
components: {
|
||||
'DataTableSubMenu': DataTableSubMenu
|
||||
}
|
||||
}
|
||||
</CodeHighlight>
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CarService from '../../service/CarService';
|
||||
import DataTableSubMenu from './DataTableSubMenu';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cars1: null,
|
||||
cars2: null,
|
||||
editingCar: null,
|
||||
editingCarIndex: null,
|
||||
originalCar: null,
|
||||
brands: [
|
||||
{brand: 'Audi', value: 'Audi'},
|
||||
{brand: 'BMW', value: 'BMW'},
|
||||
{brand: 'Fiat', value: 'Fiat'},
|
||||
{brand: 'Honda', value: 'Honda'},
|
||||
{brand: 'Jaguar', value: 'Jaguar'},
|
||||
{brand: 'Mercedes', value: 'Mercedes'},
|
||||
{brand: 'Renault', value: 'Renault'},
|
||||
{brand: 'Volkswagen', value: 'Volkswagen'},
|
||||
{brand: 'Volvo', value: 'Volvo'}
|
||||
]
|
||||
}
|
||||
},
|
||||
carService: null,
|
||||
created() {
|
||||
this.carService = new CarService();
|
||||
},
|
||||
methods: {
|
||||
onEditInit(event) {
|
||||
this.editingCarIndex = event.index;
|
||||
this.editingCar = {...event.data}; //update on input
|
||||
this.originalCar = {...event.data}; //revert with escape key
|
||||
},
|
||||
onEditComplete(event) {
|
||||
switch (event.field) {
|
||||
case 'year':
|
||||
if (this.isPositiveInteger(this.editingCar.year)) {
|
||||
Vue.set(this.cars2, this.editingCarIndex, this.editingCar);
|
||||
}
|
||||
else {
|
||||
this.$toast.add({severity:'error', summary: 'Validation Failed', detail:'Year must be a number', life: 3000});
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (this.editingCar[event.field].trim().length > 0) {
|
||||
Vue.set(this.cars2, this.editingCarIndex, this.editingCar);
|
||||
}
|
||||
else {
|
||||
this.$toast.add({severity:'error', summary: 'Validation Failed', detail: event.field + ' is required', life: 3000});
|
||||
event.preventDefault();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
clearEditorState() {
|
||||
this.editingCar = null;
|
||||
this.originalCar = null;
|
||||
},
|
||||
onEdit(newValue, props) {
|
||||
this.editingCar[props.column.field] = newValue;
|
||||
},
|
||||
onEditCancel(event) {
|
||||
Vue.set(this.cars2, event.index, this.originalCar);
|
||||
this.editingCar = null;
|
||||
},
|
||||
isPositiveInteger(val) {
|
||||
let str = String(val);
|
||||
str = str.trim();
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
str = str.replace(/^0+/, "") || "0";
|
||||
var n = Math.floor(Number(str));
|
||||
return n !== Infinity && String(n) === str && n >= 0;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.carService.getCarsSmall().then(data => this.cars1 = data);
|
||||
this.carService.getCarsSmall().then(data => this.cars2 = data);
|
||||
},
|
||||
components: {
|
||||
'DataTableSubMenu': DataTableSubMenu
|
||||
|
|
Loading…
Reference in New Issue