primevue-mirror/pages/datatable/Edit.vue

695 lines
29 KiB
Vue
Raw Normal View History

2022-09-09 20:41:18 +00:00
<template>
<div>
<div class="content-section introduction">
<div class="feature-intro">
<h1>DataTable <span>InCell Edit</span></h1>
<p>In cell editing provides a rapid and user friendly way to manipulate the data. The datatable provides a flexible API so that the editing behavior is implemented by the page author whether it utilizes v-model or vuex.</p>
</div>
<AppDemoActions />
</div>
<div class="content-section implementation p-fluid">
<div class="card">
<h5>Cell Editing</h5>
<p>Validations, dynamic columns and reverting values with the escape key.</p>
<DataTable :value="products1" editMode="cell" @cell-edit-complete="onCellEditComplete" class="editable-cells-table" responsiveLayout="scroll">
2022-12-08 12:26:57 +00:00
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%">
2022-09-09 20:41:18 +00:00
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
<div class="card">
<h5>Row Editing</h5>
2022-12-08 12:26:57 +00:00
<DataTable v-model:editingRows="editingRows" :value="products2" editMode="row" dataKey="id" @row-edit-save="onRowEditSave" responsiveLayout="scroll">
2022-09-09 20:41:18 +00:00
<Column field="code" header="Code" style="width: 20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
<Column field="name" header="Name" style="width: 20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</template>
</Column>
<Column field="inventoryStatus" header="Status" style="width: 20%">
<template #editor="{ data, field }">
<Dropdown v-model="data[field]" :options="statuses" optionLabel="label" optionValue="value" placeholder="Select a Status">
<template #option="slotProps">
<span :class="'product-badge status-' + slotProps.option.value.toLowerCase()">{{ slotProps.option.label }}</span>
</template>
</Dropdown>
</template>
<template #body="slotProps">
{{ getStatusLabel(slotProps.data.inventoryStatus) }}
</template>
</Column>
<Column field="price" header="Price" style="width: 20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</template>
</Column>
<Column :rowEditor="true" style="width: 10%; min-width: 8rem" bodyStyle="text-align:center"></Column>
</DataTable>
</div>
<div class="card">
<h5>Cell Editing with Sorting and Filter</h5>
2022-12-08 12:26:57 +00:00
<DataTable v-model:filters="filters" :value="products3" editMode="cell" @cell-edit-complete="onCellEditComplete" class="editable-cells-table" filterDisplay="row" responsiveLayout="scroll">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%" sortable filter>
2022-09-09 20:41:18 +00:00
<template #filter="{ filterModel, filterCallback }">
2022-12-08 12:26:57 +00:00
<InputText v-model="filterModel.value" v-tooltip.top.focus="'Hit enter key to filter'" type="text" @keydown.enter="filterCallback()" class="p-column-filter" />
2022-09-09 20:41:18 +00:00
</template>
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
</div>
<ClientOnly><AppDoc name="DataTableEditDemo" :sources="sources" :service="['ProductService']" :data="['products-small']" github="datatable/DataTableEditDemo.vue" /></ClientOnly>
2022-09-09 20:41:18 +00:00
</div>
</template>
<script>
import { FilterMatchMode } from 'primevue/api';
2022-12-08 12:26:57 +00:00
import ProductService from '../../service/ProductService';
2022-09-09 20:41:18 +00:00
export default {
data() {
return {
editingRows: [],
columns: null,
products1: null,
products2: null,
products3: null,
statuses: [
{ label: 'In Stock', value: 'INSTOCK' },
{ label: 'Low Stock', value: 'LOWSTOCK' },
{ label: 'Out of Stock', value: 'OUTOFSTOCK' }
],
filters: {
code: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
quantity: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
price: { value: null, matchMode: FilterMatchMode.STARTS_WITH }
},
sources: {
'options-api': {
tabName: 'Options API Source',
content: `
<template>
<div class="p-fluid">
<div class="card">
<h5>Cell Editing</h5>
<p>Validations, dynamic columns and reverting values with the escape key.</p>
<DataTable :value="products1" editMode="cell" @cell-edit-complete="onCellEditComplete" class="editable-cells-table" responsiveLayout="scroll">
<Column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field" style="width:25%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
<div class="card">
<h5>Row Editing</h5>
<DataTable :value="products2" editMode="row" dataKey="id" v-model:editingRows="editingRows" @row-edit-save="onRowEditSave" responsiveLayout="scroll">
<Column field="code" header="Code" style="width:20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
<Column field="name" header="Name" style="width:20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</template>
</Column>
<Column field="inventoryStatus" header="Status" style="width:20%">
<template #editor="{ data, field }">
<Dropdown v-model="data[field]" :options="statuses" optionLabel="label" optionValue="value" placeholder="Select a Status">
<template #option="slotProps">
<span :class="'product-badge status-' + slotProps.option.value.toLowerCase()">{{slotProps.option.label}}</span>
</template>
</Dropdown>
</template>
<template #body="slotProps">
{{getStatusLabel(slotProps.data.inventoryStatus)}}
</template>
</Column>
<Column field="price" header="Price" style="width:20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</template>
</Column>
<Column :rowEditor="true" style="width:10%; min-width:8rem" bodyStyle="text-align:center"></Column>
</DataTable>
</div>
<div class="card">
<h5>Cell Editing with Sorting and Filter</h5>
<DataTable :value="products3" editMode="cell" @cell-edit-complete="onCellEditComplete" class="editable-cells-table" filterDisplay="row" v-model:filters="filters" responsiveLayout="scroll">
<Column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field" style="width:25%" sortable filter>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" v-tooltip.top.focus="'Hit enter key to filter'"/>
</template>
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
</div>
</template>
<script>
import ProductService from './service/ProductService';
import {FilterMatchMode} from 'primevue/api';
export default {
data() {
return {
editingRows: [],
columns: null,
products1: null,
products2: null,
products3: null,
statuses: [
{label: 'In Stock', value: 'INSTOCK'},
{label: 'Low Stock', value: 'LOWSTOCK'},
{label: 'Out of Stock', value: 'OUTOFSTOCK'}
],
filters: {
'code': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'name': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'quantity': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'price': {value: null, matchMode: FilterMatchMode.STARTS_WITH}
}
}
},
productService: null,
created() {
this.productService = new ProductService();
this.columns = [
{field: 'code', header: 'Code'},
{field: 'name', header: 'Name'},
{field: 'quantity', header: 'Quantity'},
{field: 'price', header: 'Price'}
];
},
methods: {
onCellEditComplete(event) {
let { data, newValue, field } = event;
switch (field) {
case 'quantity':
case 'price':
if (this.isPositiveInteger(newValue))
data[field] = newValue;
else
event.preventDefault();
break;
default:
if (newValue.trim().length > 0)
data[field] = newValue;
else
event.preventDefault();
break;
}
},
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;
},
onRowEditSave(event) {
let { newData, index } = event;
this.products2[index] = newData;
},
getStatusLabel(status) {
switch(status) {
case 'INSTOCK':
return 'In Stock';
case 'LOWSTOCK':
return 'Low Stock';
case 'OUTOFSTOCK':
return 'Out of Stock';
default:
return 'NA';
}
}
},
mounted() {
this.productService.getProductsSmall().then(data => this.products1 = data);
this.productService.getProductsSmall().then(data => this.products2 = data);
this.productService.getProductsSmall().then(data => this.products3 = data);
}
}
<\\/script>
<style lang="scss" scoped>
::v-deep(.editable-cells-table td.p-cell-editing) {
padding-top: 0;
padding-bottom: 0;
}
</style>`
},
'composition-api': {
tabName: 'Composition API Source',
content: `
<template>
<div class="p-fluid">
<div class="card">
<h5>Cell Editing</h5>
<p>Validations, dynamic columns and reverting values with the escape key.</p>
<DataTable :value="products1" editMode="cell" @cell-edit-complete="onCellEditComplete" class="editable-cells-table" responsiveLayout="scroll">
<Column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field" style="width:25%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
<div class="card">
<h5>Row Editing</h5>
<DataTable :value="products2" editMode="row" dataKey="id" v-model:editingRows="editingRows" @row-edit-save="onRowEditSave" responsiveLayout="scroll">
<Column field="code" header="Code" style="width:20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
<Column field="name" header="Name" style="width:20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</template>
</Column>
<Column field="inventoryStatus" header="Status" style="width:20%">
<template #editor="{ data, field }">
<Dropdown v-model="data[field]" :options="statuses" optionLabel="label" optionValue="value" placeholder="Select a Status">
<template #option="slotProps">
<span :class="'product-badge status-' + slotProps.option.value.toLowerCase()">{{slotProps.option.label}}</span>
</template>
</Dropdown>
</template>
<template #body="slotProps">
{{getStatusLabel(slotProps.data.inventoryStatus)}}
</template>
</Column>
<Column field="price" header="Price" style="width:20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</template>
</Column>
<Column :rowEditor="true" style="width:10%; min-width:8rem" bodyStyle="text-align:center"></Column>
</DataTable>
</div>
<div class="card">
<h5>Cell Editing with Sorting and Filter</h5>
<DataTable :value="products3" editMode="cell" @cell-edit-complete="onCellEditComplete" class="editable-cells-table" filterDisplay="row" v-model:filters="filters" responsiveLayout="scroll">
<Column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field" style="width:25%" sortable filter>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" v-tooltip.top.focus="'Hit enter key to filter'"/>
</template>
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import ProductService from './service/ProductService';
import {FilterMatchMode} from 'primevue/api';
export default {
setup() {
onMounted(() => {
productService.value.getProductsSmall().then(data => products1.value = data);
productService.value.getProductsSmall().then(data => products2.value = data);
productService.value.getProductsSmall().then(data => products3.value = data);
});
const productService = ref(new ProductService());
const editingRows = ref([]);
const columns = ref([
{field: 'code', header: 'Code'},
{field: 'name', header: 'Name'},
{field: 'quantity', header: 'Quantity'},
{field: 'price', header: 'Price'}
]);
const products1 = ref(null);
const products2 = ref(null);
const products3 = ref(null);
const statuses = ref([
{label: 'In Stock', value: 'INSTOCK'},
{label: 'Low Stock', value: 'LOWSTOCK'},
{label: 'Out of Stock', value: 'OUTOFSTOCK'}
]);
const filters = ref({
'code': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'name': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'quantity': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'price': {value: null, matchMode: FilterMatchMode.STARTS_WITH}
});
const onCellEditComplete = (event) => {
let { data, newValue, field } = event;
switch (field) {
case 'quantity':
case 'price':
if (isPositiveInteger(newValue))
data[field] = newValue;
else
event.preventDefault();
break;
default:
if (newValue.trim().length > 0)
data[field] = newValue;
else
event.preventDefault();
break;
}
};
const 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;
};
const onRowEditSave = (event) => {
let { newData, index } = event;
products2.value[index] = newData;
};
const getStatusLabel = (status) => {
switch(status) {
case 'INSTOCK':
return 'In Stock';
case 'LOWSTOCK':
return 'Low Stock';
case 'OUTOFSTOCK':
return 'Out of Stock';
default:
return 'NA';
}
};
return { productService, editingRows, columns, products1, products2, products3, statuses, filters, onCellEditComplete,
isPositiveInteger, onRowEditSave, getStatusLabel }
}
}
<\\/script>
<style lang="scss" scoped>
::v-deep(.editable-cells-table td.p-cell-editing) {
padding-top: 0;
padding-bottom: 0;
}
</style>`
},
'browser-source': {
tabName: 'Browser Source',
imports: `<script src="https://unpkg.com/primevue@^3/datatable/datatable.min.js"><\\/script>
<script src="https://unpkg.com/primevue@^3/column/column.min.js"><\\/script>
<script src="https://unpkg.com/primevue@^3/dropdown/dropdown.min.js"><\\/script>
<script src="./ProductService.js"><\\/script>`,
content: `<div id="app" class="p-fluid card">
<div class="card">
<h5>Cell Editing</h5>
<p>Validations, dynamic columns and reverting values with the escape key.</p>
<p-datatable :value="products1" edit-mode="cell" @cell-edit-complete="onCellEditComplete" class="editable-cells-table" responsive-layout="scroll">
<p-column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field" style="width:25%">
<template #editor="{ data, field }">
<p-inputtext v-model="data[field]" autofocus></p-inputtext>
</template>
</p-column>
</p-datatable>
</div>
<div class="card">
<h5>Row Editing</h5>
<p-datatable :value="products2" edit-mode="row" dataKey="id" v-model:editing-rows="editingRows" @row-edit-save="onRowEditSave" responsive-layout="scroll">
<p-column field="code" header="Code" style="width:20%">
<template #editor="{ data, field }">
<p-inputtext v-model="data[field]" autofocus></p-inputtext>
</template>
</p-column>
<p-column field="name" header="Name" style="width:20%">
<template #editor="{ data, field }">
<p-inputtext v-model="data[field]"></p-inputtext>
</template>
</p-column>
<p-column field="inventoryStatus" header="Status" style="width:20%">
<template #editor="{ data, field }">
<p-dropdown v-model="data[field]" :options="statuses" option-label="label" option-value="value" placeholder="Select a Status">
<template #option="slotProps">
<span :class="'product-badge status-' + slotProps.option.value.toLowerCase()">{{slotProps.option.label}}</span>
</template>
</p-dropdown>
</template>
<template #body="slotProps">
{{getStatusLabel(slotProps.data.inventoryStatus)}}
</template>
</p-column>
<p-column field="price" header="Price" style="width:20%">
<template #editor="{ data, field }">
<p-inputtext v-model="data[field]"></p-inputtext>
</template>
</p-column>
<p-column :row-editor="true" style="width:10%; min-width:8rem" bodyStyle="text-align:center"></p-column>
</p-datatable>
</div>
<div class="card">
<h5>Cell Editing with Sorting and Filter</h5>
<p-datatable :value="products3" editMode="cell" @cell-edit-complete="onCellEditComplete" class="editable-cells-table" filterDisplay="row" v-model:filters="filters" responsiveLayout="scroll">
<p-column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field" style="width:25%" sortable filter>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" v-tooltip.top.focus="'Hit enter key to filter'"/>
</template>
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</p-datatable>
</div>
</div>
<script type="module">
const { createApp, ref, onMounted } = Vue;
const { FilterMatchMode } = primevue.api;
const App = {
setup() {
onMounted(() => {
productService.value.getProductsSmall().then(data => products1.value = data);
productService.value.getProductsSmall().then(data => products2.value = data);
productService.value.getProductsSmall().then(data => products3.value = data);
});
const productService = ref(new ProductService());
const editingRows = ref([]);
const columns = ref([
{field: 'code', header: 'Code'},
{field: 'name', header: 'Name'},
{field: 'quantity', header: 'Quantity'},
{field: 'price', header: 'Price'}
]);
const products1 = ref(null);
const products2 = ref(null);
const products3 = ref(null);
const statuses = ref([
{label: 'In Stock', value: 'INSTOCK'},
{label: 'Low Stock', value: 'LOWSTOCK'},
{label: 'Out of Stock', value: 'OUTOFSTOCK'}
]);
const filters = ref({
'code': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'name': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'quantity': {value: null, matchMode: FilterMatchMode.STARTS_WITH},
'price': {value: null, matchMode: FilterMatchMode.STARTS_WITH}
});
const onCellEditComplete = (event) => {
let { data, newValue, field } = event;
switch (field) {
case 'quantity':
case 'price':
if (isPositiveInteger(newValue))
data[field] = newValue;
else
event.preventDefault();
break;
default:
if (newValue.trim().length > 0)
data[field] = newValue;
else
event.preventDefault();
break;
}
};
const 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;
};
const onRowEditSave = (event) => {
let { newData, index } = event;
products2.value[index] = newData;
};
const getStatusLabel = (status) => {
switch(status) {
case 'INSTOCK':
return 'In Stock';
case 'LOWSTOCK':
return 'Low Stock';
case 'OUTOFSTOCK':
return 'Out of Stock';
default:
return 'NA';
}
};
return { productService, editingRows, columns, products1, products2, products3, statuses, filters, onCellEditComplete,
isPositiveInteger, onRowEditSave, getStatusLabel }
},
components: {
"p-datatable": primevue.datatable,
"p-column": primevue.column,
"p-dropdown": primevue.dropdown,
"p-inputtext": primevue.inputtext
}
};
createApp(App)
.use(primevue.config.default)
.mount("#app");
<\\/script>
<style>
.editable-cells-table td.p-cell-editing {
padding-top: 0;
padding-bottom: 0;
}
</style>`
}
}
};
},
productService: null,
created() {
this.productService = new ProductService();
this.columns = [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'quantity', header: 'Quantity' },
{ field: 'price', header: 'Price' }
];
},
2022-12-08 12:26:57 +00:00
mounted() {
this.productService.getProductsSmall().then((data) => (this.products1 = data));
this.productService.getProductsSmall().then((data) => (this.products2 = data));
this.productService.getProductsSmall().then((data) => (this.products3 = data));
},
2022-09-09 20:41:18 +00:00
methods: {
onCellEditComplete(event) {
let { data, newValue, field } = event;
switch (field) {
case 'quantity':
case 'price':
if (this.isPositiveInteger(newValue)) data[field] = newValue;
else event.preventDefault();
break;
default:
if (newValue.trim().length > 0) data[field] = newValue;
else event.preventDefault();
break;
}
},
isPositiveInteger(val) {
let str = String(val);
2022-12-08 12:26:57 +00:00
2022-09-09 20:41:18 +00:00
str = str.trim();
2022-12-08 12:26:57 +00:00
2022-09-09 20:41:18 +00:00
if (!str) {
return false;
}
2022-12-08 12:26:57 +00:00
2022-09-09 20:41:18 +00:00
str = str.replace(/^0+/, '') || '0';
var n = Math.floor(Number(str));
2022-12-08 12:26:57 +00:00
2022-09-09 20:41:18 +00:00
return n !== Infinity && String(n) === str && n >= 0;
},
onRowEditSave(event) {
let { newData, index } = event;
this.products2[index] = newData;
},
getStatusLabel(status) {
switch (status) {
case 'INSTOCK':
return 'In Stock';
case 'LOWSTOCK':
return 'Low Stock';
case 'OUTOFSTOCK':
return 'Out of Stock';
default:
return 'NA';
}
}
}
};
</script>
<style lang="scss" scoped>
::v-deep(.editable-cells-table td.p-cell-editing) {
padding-top: 0;
padding-bottom: 0;
}
</style>