Merged new Docs and Demos

This commit is contained in:
Cagatay Civici 2023-02-28 11:29:30 +03:00
parent 296cc217fb
commit dfcc8ef4e7
1235 changed files with 130757 additions and 122640 deletions

View file

@ -0,0 +1,175 @@
<template>
<DocSectionText id="accessibility" label="Accessibility" v-bind="$attrs">
<h3>Screen Reader</h3>
<p>
DataTable uses a <i>table</i> element whose attributes can be extended with the <i>tableProps</i> option. This property allows passing aria roles and attributes like <i>aria-label</i> and <i>aria-describedby</i> to define the table for
readers. Default role of the table is <i>table</i>. Header, body and footer elements use <i>rowgroup</i>, rows use <i>row</i> role, header cells have <i>columnheader</i> and body cells use <i>cell</i> roles. Sortable headers utilizer
<i>aria-sort</i> attribute either set to "ascending" or "descending".
</p>
<p>
Built-in checkbox and radiobutton components for row selection use <i>checkbox</i> and <i>radiobutton</i>. The label to describe them is retrieved from the <i>aria.selectRow</i> and <i>aria.unselectRow</i> properties of the
<NuxtLink to="/locale">locale</NuxtLink> API. Similarly header checkbox uses <i>selectAll</i> and <i>unselectAll</i> keys. When a row is selected, <i>aria-selected</i> is set to true on a row.
</p>
<p>
The element to expand or collapse a row is a <i>button</i> with <i>aria-expanded</i> and <i>aria-controls</i> properties. Value to describe the buttons is derived from <i>aria.expandRow</i> and <i>aria.collapseRow</i> properties of the
<NuxtLink to="/locale">locale</NuxtLink> API.
</p>
<p>
The filter menu button use <i>aria.showFilterMenu</i> and <i>aria.hideFilterMenu</i> properties as <i>aria-label</i> in addition to the <i>aria-haspopup</i>, <i>aria-expanded</i> and <i>aria-controls</i> to define the relation between the
button and the overlay. Popop menu has <i>dialog</i> role with <i>aria-modal</i> as focus is kept within the overlay. The operator dropdown use <i>aria.filterOperator</i> and filter constraints dropdown use
<i>aria.filterConstraint</i> properties. Buttons to add rules on the other hand utilize <i>aria.addRule</i> and <i>aria.removeRule</i> properties. The footer buttons similarly use <i>aria.clear</i> and <i>aria.apply</i> properties.
<i>filterInputProps</i> of the Column component can be used to define aria labels for the built-in filter components, if a custom component is used with templating you also may define your own aria labels as well.
</p>
<p>
Editable cells use custom templating so you need to manage aria roles and attributes manually if required. The row editor controls are button elements with <i>aria.editRow</i>, <i>aria.cancelEdit</i> and <i>aria.saveEdit</i> used for the
<i>aria-label</i>.
</p>
<p>Paginator is a standalone component used inside the DataTable, refer to the <NuxtLink to="/paginator">paginator</NuxtLink> for more information about the accessibility features.</p>
<h3>Keyboard Support</h3>
<p>Any button element inside the DataTable used for cases like filter, row expansion, edit are tabbable and can be used with <i>space</i> and <i>enter</i> keys.</p>
<h3>Sortable Headers Keyboard Support</h3>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i>tab</i>
</td>
<td>Moves through the headers.</td>
</tr>
<tr>
<td>
<i>enter</i>
</td>
<td>Sorts the column.</td>
</tr>
<tr>
<td>
<i>space</i>
</td>
<td>Sorts the column.</td>
</tr>
</tbody>
</table>
</div>
<h3>Filter Menu Keyboard Support</h3>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i>tab</i>
</td>
<td>Moves through the elements inside the popup.</td>
</tr>
<tr>
<td>
<i>escape</i>
</td>
<td>Hides the popup.</td>
</tr>
</tbody>
</table>
</div>
<h3>Selection Keyboard Support</h3>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Key</th>
<th>Function</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i>tab</i>
</td>
<td>Moves focus to the first selected row, if there is none then first row receives the focus.</td>
</tr>
<tr>
<td>
<i>up arrow</i>
</td>
<td>Moves focus to the previous row.</td>
</tr>
<tr>
<td>
<i>down arrow</i>
</td>
<td>Moves focus to the next row.</td>
</tr>
<tr>
<td>
<i>enter</i>
</td>
<td>Toggles the selected state of the focused row depending on the metaKeySelection setting.</td>
</tr>
<tr>
<td>
<i>space</i>
</td>
<td>Toggles the selected state of the focused row depending on the metaKeySelection setting.</td>
</tr>
<tr>
<td>
<i>home</i>
</td>
<td>Moves focus to the first row.</td>
</tr>
<tr>
<td>
<i>end</i>
</td>
<td>Moves focus to the last row.</td>
</tr>
<tr>
<td><i>shift</i> + <i>down arrow</i></td>
<td>Moves focus to the next row and toggles the selection state.</td>
</tr>
<tr>
<td><i>shift</i> + <i>up arrow</i></td>
<td>Moves focus to the previous row and toggles the selection state.</td>
</tr>
<tr>
<td><i>shift</i> + <i>space</i></td>
<td>Selects the rows between the most recently selected row and the focused row.</td>
</tr>
<tr>
<td><i>control</i> + <i>shift</i> + <i>home</i></td>
<td>Selects the focused rows and all the options up to the first one.</td>
</tr>
<tr>
<td><i>control</i> + <i>shift</i> + <i>end</i></td>
<td>Selects the focused rows and all the options down to the last one.</td>
</tr>
<tr>
<td><i>control</i> + <i>a</i></td>
<td>Selects all rows.</td>
</tr>
</tbody>
</table>
</div>
</DocSectionText>
</template>

102
doc/datatable/BasicDoc.vue Normal file
View file

@ -0,0 +1,102 @@
<template>
<DocSectionText v-bind="$attrs">
<p>DataTable requires a <i>value</i> as data to display and <i>Column</i> components as children for the representation.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,335 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Columns can be grouped within a <i>Row</i> component and groups can be displayed within a ColumnGroup component. These groups can be displayed using <i>type</i> property that can be <i>header</i> or <i>footer</i>. Number of cells and rows
to span are defined with the <i>colspan</i> and <i>rowspan</i> properties of a Column.
</p>
</DocSectionText>
<div class="card">
<DataTable :value="sales" tableStyle="min-width: 50rem">
<ColumnGroup type="header">
<Row>
<Column header="Product" :rowspan="3" />
<Column header="Sale Rate" :colspan="4" />
</Row>
<Row>
<Column header="Sales" :colspan="2" />
<Column header="Profits" :colspan="2" />
</Row>
<Row>
<Column header="Last Year" sortable field="lastYearSale" />
<Column header="This Year" sortable field="thisYearSale" />
<Column header="Last Year" sortable field="lastYearProfit" />
<Column header="This Year" sortable field="thisYearProfit" />
</Row>
</ColumnGroup>
<Column field="product" />
<Column field="lastYearSale">
<template #body="slotProps"> {{ slotProps.data.lastYearSale }}% </template>
</Column>
<Column field="thisYearSale">
<template #body="slotProps"> {{ slotProps.data.thisYearSale }}% </template>
</Column>
<Column field="lastYearProfit">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.lastYearProfit) }}
</template>
</Column>
<Column field="thisYearProfit">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.thisYearProfit) }}
</template>
</Column>
<ColumnGroup type="footer">
<Row>
<Column footer="Totals:" :colspan="3" footerStyle="text-align:right" />
<Column :footer="lastYearTotal" />
<Column :footer="thisYearTotal" />
</Row>
</ColumnGroup>
</DataTable>
</div>
<DocSectionCode :code="code" />
</template>
<script>
export default {
data() {
return {
sales: null,
code: {
basic: `
<DataTable :value="sales" tableStyle="min-width: 50rem">
<ColumnGroup type="header">
<Row>
<Column header="Product" :rowspan="3" />
<Column header="Sale Rate" :colspan="4" />
</Row>
<Row>
<Column header="Sales" :colspan="2" />
<Column header="Profits" :colspan="2" />
</Row>
<Row>
<Column header="Last Year" sortable field="lastYearSale" />
<Column header="This Year" sortable field="thisYearSale" />
<Column header="Last Year" sortable field="lastYearProfit" />
<Column header="This Year" sortable field="thisYearProfit" />
</Row>
</ColumnGroup>
<Column field="product" />
<Column field="lastYearSale">
<template #body="slotProps"> {{ slotProps.data.lastYearSale }}% </template>
</Column>
<Column field="thisYearSale">
<template #body="slotProps"> {{ slotProps.data.thisYearSale }}% </template>
</Column>
<Column field="lastYearProfit">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.lastYearProfit) }}
</template>
</Column>
<Column field="thisYearProfit">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.thisYearProfit) }}
</template>
</Column>
<ColumnGroup type="footer">
<Row>
<Column footer="Totals:" :colspan="3" footerStyle="text-align:right" />
<Column :footer="lastYearTotal" />
<Column :footer="thisYearTotal" />
</Row>
</ColumnGroup>
</DataTable>`,
options: `
<template>
<DataTable :value="sales" tableStyle="min-width: 50rem">
<ColumnGroup type="header">
<Row>
<Column header="Product" :rowspan="3" />
<Column header="Sale Rate" :colspan="4" />
</Row>
<Row>
<Column header="Sales" :colspan="2" />
<Column header="Profits" :colspan="2" />
</Row>
<Row>
<Column header="Last Year" sortable field="lastYearSale"/>
<Column header="This Year" sortable field="thisYearSale"/>
<Column header="Last Year" sortable field="lastYearProfit"/>
<Column header="This Year" sortable field="thisYearProfit"/>
</Row>
</ColumnGroup>
<Column field="product" />
<Column field="lastYearSale">
<template #body="slotProps">
{{slotProps.data.lastYearSale}}%
</template>
</Column>
<Column field="thisYearSale">
<template #body="slotProps">
{{slotProps.data.thisYearSale}}%
</template>
</Column>
<Column field="lastYearProfit">
<template #body="slotProps">
{{formatCurrency(slotProps.data.lastYearProfit)}}
</template>
</Column>
<Column field="thisYearProfit">
<template #body="slotProps">
{{formatCurrency(slotProps.data.thisYearProfit)}}
</template>
</Column>
<ColumnGroup type="footer">
<Row>
<Column footer="Totals:" :colspan="3" footerStyle="text-align:right"/>
<Column :footer="lastYearTotal" />
<Column :footer="thisYearTotal" />
</Row>
</ColumnGroup>
</DataTable>
</template>
<script>
export default {
data() {
return {
sales: null
}
},
created() {
this.sales = [
{product: 'Bamboo Watch', lastYearSale: 51, thisYearSale: 40, lastYearProfit: 54406, thisYearProfit: 43342},
{product: 'Black Watch', lastYearSale: 83, thisYearSale: 9, lastYearProfit: 423132, thisYearProfit: 312122},
{product: 'Blue Band', lastYearSale: 38, thisYearSale: 5, lastYearProfit: 12321, thisYearProfit: 8500},
{product: 'Blue T-Shirt', lastYearSale: 49, thisYearSale: 22, lastYearProfit: 745232, thisYearProfit: 65323},
{product: 'Brown Purse', lastYearSale: 17, thisYearSale: 79, lastYearProfit: 643242, thisYearProfit: 500332},
{product: 'Chakra Bracelet', lastYearSale: 52, thisYearSale: 65, lastYearProfit: 421132, thisYearProfit: 150005},
{product: 'Galaxy Earrings', lastYearSale: 82, thisYearSale: 12, lastYearProfit: 131211, thisYearProfit: 100214},
{product: 'Game Controller', lastYearSale: 44, thisYearSale: 45, lastYearProfit: 66442, thisYearProfit: 53322},
{product: 'Gaming Set', lastYearSale: 90, thisYearSale: 56, lastYearProfit: 765442, thisYearProfit: 296232},
{product: 'Gold Phone Case', lastYearSale: 75, thisYearSale: 54, lastYearProfit: 21212, thisYearProfit: 12533}
];
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
}
},
computed: {
lastYearTotal() {
let total = 0;
for(let sale of this.sales) {
total += sale.lastYearProfit;
}
return this.formatCurrency(total);
},
thisYearTotal() {
let total = 0;
for(let sale of this.sales) {
total += sale.thisYearProfit;
}
return this.formatCurrency(total);
}
}
}
<\\/script>
`,
composition: `
<template>
<DataTable :value="sales" tableStyle="min-width: 50rem">
<ColumnGroup type="header">
<Row>
<Column header="Product" :rowspan="3" />
<Column header="Sale Rate" :colspan="4" />
</Row>
<Row>
<Column header="Sales" :colspan="2" />
<Column header="Profits" :colspan="2" />
</Row>
<Row>
<Column header="Last Year" sortable field="lastYearSale"/>
<Column header="This Year" sortable field="thisYearSale"/>
<Column header="Last Year" sortable field="lastYearProfit"/>
<Column header="This Year" sortable field="thisYearProfit"/>
</Row>
</ColumnGroup>
<Column field="product" />
<Column field="lastYearSale">
<template #body="slotProps">
{{slotProps.data.lastYearSale}}%
</template>
</Column>
<Column field="thisYearSale">
<template #body="slotProps">
{{slotProps.data.thisYearSale}}%
</template>
</Column>
<Column field="lastYearProfit">
<template #body="slotProps">
{{formatCurrency(slotProps.data.lastYearProfit)}}
</template>
</Column>
<Column field="thisYearProfit">
<template #body="slotProps">
{{formatCurrency(slotProps.data.thisYearProfit)}}
</template>
</Column>
<ColumnGroup type="footer">
<Row>
<Column footer="Totals:" :colspan="3" footerStyle="text-align:right"/>
<Column :footer="lastYearTotal" />
<Column :footer="thisYearTotal" />
</Row>
</ColumnGroup>
</DataTable>
</template>
<script setup>
import { ref, computed } from 'vue';
const sales = ref([
{product: 'Bamboo Watch', lastYearSale: 51, thisYearSale: 40, lastYearProfit: 54406, thisYearProfit: 43342},
{product: 'Black Watch', lastYearSale: 83, thisYearSale: 9, lastYearProfit: 423132, thisYearProfit: 312122},
{product: 'Blue Band', lastYearSale: 38, thisYearSale: 5, lastYearProfit: 12321, thisYearProfit: 8500},
{product: 'Blue T-Shirt', lastYearSale: 49, thisYearSale: 22, lastYearProfit: 745232, thisYearProfit: 65323},
{product: 'Brown Purse', lastYearSale: 17, thisYearSale: 79, lastYearProfit: 643242, thisYearProfit: 500332},
{product: 'Chakra Bracelet', lastYearSale: 52, thisYearSale: 65, lastYearProfit: 421132, thisYearProfit: 150005},
{product: 'Galaxy Earrings', lastYearSale: 82, thisYearSale: 12, lastYearProfit: 131211, thisYearProfit: 100214},
{product: 'Game Controller', lastYearSale: 44, thisYearSale: 45, lastYearProfit: 66442, thisYearProfit: 53322},
{product: 'Gaming Set', lastYearSale: 90, thisYearSale: 56, lastYearProfit: 765442, thisYearProfit: 296232},
{product: 'Gold Phone Case', lastYearSale: 75, thisYearSale: 54, lastYearProfit: 21212, thisYearProfit: 12533}
]);
const formatCurrency = (value) => {
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
};
const lastYearTotal = computed(() => {
let total = 0;
for(let sale of sales.value) {
total += sale.lastYearProfit;
}
return formatCurrency(total);
});
const thisYearTotal = computed(() => {
let total = 0;
for(let sale of sales.value) {
total += sale.thisYearProfit;
}
return formatCurrency(total);
});
<\/script>
`
}
};
},
created() {
this.sales = [
{ product: 'Bamboo Watch', lastYearSale: 51, thisYearSale: 40, lastYearProfit: 54406, thisYearProfit: 43342 },
{ product: 'Black Watch', lastYearSale: 83, thisYearSale: 9, lastYearProfit: 423132, thisYearProfit: 312122 },
{ product: 'Blue Band', lastYearSale: 38, thisYearSale: 5, lastYearProfit: 12321, thisYearProfit: 8500 },
{ product: 'Blue T-Shirt', lastYearSale: 49, thisYearSale: 22, lastYearProfit: 745232, thisYearProfit: 65323 },
{ product: 'Brown Purse', lastYearSale: 17, thisYearSale: 79, lastYearProfit: 643242, thisYearProfit: 500332 },
{ product: 'Chakra Bracelet', lastYearSale: 52, thisYearSale: 65, lastYearProfit: 421132, thisYearProfit: 150005 },
{ product: 'Galaxy Earrings', lastYearSale: 82, thisYearSale: 12, lastYearProfit: 131211, thisYearProfit: 100214 },
{ product: 'Game Controller', lastYearSale: 44, thisYearSale: 45, lastYearProfit: 66442, thisYearProfit: 53322 },
{ product: 'Gaming Set', lastYearSale: 90, thisYearSale: 56, lastYearProfit: 765442, thisYearProfit: 296232 },
{ product: 'Gold Phone Case', lastYearSale: 75, thisYearSale: 54, lastYearProfit: 21212, thisYearProfit: 12533 }
];
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
}
},
computed: {
lastYearTotal() {
let total = 0;
for (let sale of this.sales) {
total += sale.lastYearProfit;
}
return this.formatCurrency(total);
},
thisYearTotal() {
let total = 0;
for (let sale of this.sales) {
total += sale.thisYearProfit;
}
return this.formatCurrency(total);
}
}
};
</script>

View file

@ -0,0 +1,144 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Column visibility based on a condition can be implemented with dynamic columns, in this sample a MultiSelect is used to manage the visible columns.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<template #header>
<div style="text-align: left">
<MultiSelect :modelValue="selectedColumns" :options="columns" optionLabel="header" @update:modelValue="onToggle" display="chip" placeholder="Select Columns" />
</div>
</template>
<Column field="code" header="Code" />
<Column v-for="(col, index) of selectedColumns" :key="col.field + '_' + index" :field="col.field" :header="col.header"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
selectedColumns: null,
columns: null,
products: null,
code: {
basic: `
<DataTable :value="products" tableStyle="min-width: 50rem">
<template #header>
<div style="text-align:left">
<MultiSelect :modelValue="selectedColumns" :options="columns" optionLabel="header" @update:modelValue="onToggle"
display="chip" placeholder="Select Columns" />
</div>
</template>
<Column field="code" header="Code" />
<Column v-for="(col, index) of selectedColumns" :field="col.field" :header="col.header" :key="col.field + '_' + index"></Column>
</DataTable>`,
options: `
<template>
<div>
<DataTable :value="products" tableStyle="min-width: 50rem">
<template #header>
<div style="text-align:left">
<MultiSelect :modelValue="selectedColumns" :options="columns" optionLabel="header" @update:modelValue="onToggle"
display="chip" placeholder="Select Columns" />
</div>
</template>
<Column field="code" header="Code" />
<Column v-for="(col, index) of selectedColumns" :field="col.field" :header="col.header" :key="col.field + '_' + index"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
selectedColumns: null,
columns: null,
products: null
}
},
created() {
this.columns = [
{field: 'name', header: 'Name'},
{field: 'category', header: 'Category'},
{field: 'quantity', header: 'Quantity'}
];
this.selectedColumns = this.columns;
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onToggle(value) {
this.selectedColumns = this.columns.filter(col => value.includes(col));
}
}
}
<\/script>
`,
composition: `
<template>
<div>
<DataTable :value="products" tableStyle="min-width: 50rem">
<template #header>
<div style="text-align:left">
<MultiSelect :modelValue="selectedColumns" :options="columns" optionLabel="header" @update:modelValue="onToggle"
display="chip" placeholder="Select Columns" />
</div>
</template>
<Column field="code" header="Code" />
<Column v-for="(col, index) of selectedColumns" :field="col.field" :header="col.header" :key="col.field + '_' + index"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const columns = ref([
{field: 'name', header: 'Name'},
{field: 'category', header: 'Category'},
{field: 'quantity', header: 'Quantity'}
]);
const selectedColumns = ref(columns.value);
const products = ref();
const onToggle = (val) => {
selectedColumns.value = columns.value.filter(col => val.includes(col));
};
<\/script>
`
}
};
},
created() {
this.columns = [
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
];
this.selectedColumns = this.columns;
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onToggle(value) {
this.selectedColumns = this.columns.filter((col) => value.includes(col));
}
}
};
</script>

View file

@ -0,0 +1,169 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Particular rows and cells can be styled based on conditions. The <i>rowClass</i> receives a row data as a parameter to return a style class for a row whereas cells are customized using the <i>body</i> template.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" :rowClass="rowClass" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity">
<template #body="slotProps">
<div :class="stockClass(slotProps.data)">
{{ slotProps.data.quantity }}
</div>
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" :rowClass="rowClass" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity">
<template #body="slotProps">
<div :class="stockClass(slotProps.data)">
{{ slotProps.data.quantity }}
</div>
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" :rowClass="rowClass" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity">
<template #body="slotProps">
<div :class="stockClass(slotProps.data)">
{{ slotProps.data.quantity }}
</div>
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsSmall().then((data) => (this.products = data));
},
methods: {
rowClass(data) {
return [{ 'bg-primary': data.category === 'Fitness' }];
},
stockClass(data) {
return [
'border-circle w-2rem h-2rem inline-flex font-bold justify-content-center align-items-center text-sm',
{
'bg-red-100 text-red-900': data.quantity === 0,
'bg-blue-100 text-blue-900': data.quantity > 0 && data.quantity < 10,
'bg-teal-100 text-teal-900': data.quantity > 10
}
];
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" :rowClass="rowClass" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity">
<template #body="slotProps">
<div :class="stockClass(slotProps.data)">
{{ slotProps.data.quantity }}
</div>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsSmall().then((data) => (this.products = data));
});
const products = ref();
const rowClass = (data) => {
return [{ 'bg-primary': data.category === 'Fitness' }];
};
const stockClass = (data) => {
return [
'border-circle w-2rem h-2rem inline-flex font-bold justify-content-center align-items-center text-sm',
{
'bg-red-100 text-red-900': data.quantity === 0,
'bg-blue-100 text-blue-900': data.quantity > 0 && data.quantity < 10,
'bg-teal-100 text-teal-900': data.quantity > 10
}
];
};
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...`
}
};
},
mounted() {
ProductService.getProductsSmall().then((data) => (this.products = data));
},
methods: {
rowClass(data) {
return [{ 'bg-primary': data.category === 'Fitness' }];
},
stockClass(data) {
return [
'border-circle w-2rem h-2rem inline-flex font-bold justify-content-center align-items-center text-sm',
{
'bg-red-100 text-red-900': data.quantity === 0,
'bg-blue-100 text-blue-900': data.quantity > 0 && data.quantity < 10,
'bg-teal-100 text-teal-900': data.quantity > 10
}
];
}
}
};
</script>

View file

@ -0,0 +1,180 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
DataTable has exclusive integration with ContextMenu using the <i>contextMenu</i> event to open a menu on right click alont with <i>contextMenuSelection</i> property and <i>row-contextmenu</i> event to control the selection via the menu.
</p>
</DocSectionText>
<div class="card">
<ContextMenu ref="cm" :model="menuModel" />
<DataTable v-model:contextMenuSelection="selectedProduct" :value="products" contextMenu @row-contextmenu="onRowContextMenu" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
menuModel: [
{ label: 'View', icon: 'pi pi-fw pi-search', command: () => this.viewProduct(this.selectedProduct) },
{ label: 'Delete', icon: 'pi pi-fw pi-times', command: () => this.deleteProduct(this.selectedProduct) }
],
code: {
basic: `
<ContextMenu ref="cm" :model="menuModel" />
<DataTable v-model:contextMenuSelection="selectedProduct" :value="products" contextMenu
@row-contextmenu="onRowContextMenu" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
</DataTable>`,
options: `
<template>
<div>
<ContextMenu ref="cm" :model="menuModel" />
<DataTable :value="products" contextMenu v-model:contextMenuSelection="selectedProduct"
@rowContextmenu="onRowContextMenu" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{formatCurrency(slotProps.data.price)}}
</template>
</Column>
</DataTable>
<Toast />
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
menuModel: [
{label: 'View', icon: 'pi pi-fw pi-search', command: () => this.viewProduct(this.selectedProduct)},
{label: 'Delete', icon: 'pi pi-fw pi-times', command: () => this.deleteProduct(this.selectedProduct)}
]
}
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onRowContextMenu(event) {
this.$refs.cm.show(event.originalEvent);
},
viewProduct(product) {
this.$toast.add({severity: 'info', summary: 'Product Selected', detail: product.name, life: 3000});
},
deleteProduct(product) {
this.products = this.products.filter((p) => p.id !== product.id);
this.$toast.add({severity: 'error', summary: 'Product Deleted', detail: product.name, life: 3000});
this.selectedProduct = null;
},
formatCurrency(value) {
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
}
}
}
<\/script>
`,
composition: `
<template>
<div>
<ContextMenu ref="cm" :model="menuModel" />
<DataTable :value="products" contextMenu v-model:contextMenuSelection="selectedProduct"
@rowContextmenu="onRowContextMenu" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{formatCurrency(slotProps.data.price)}}
</template>
</Column>
</DataTable>
<Toast />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useToast } from 'primevue/usetoast';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const cm = ref();
const toast = useToast();
const products = ref();
const selectedProduct = ref();
const menuModel = ref([
{label: 'View', icon: 'pi pi-fw pi-search', command: () => viewProduct(selectedProduct)},
{label: 'Delete', icon: 'pi pi-fw pi-times', command: () => deleteProduct(selectedProduct)}
]);
const onRowContextMenu = (event) => {
cm.value.show(event.originalEvent);
};
const viewProduct = (product) => {
toast.add({severity: 'info', summary: 'Product Selected', detail: product.value.name, life: 3000});
};
const deleteProduct = (product) => {
products.value = products.value.filter((p) => p.id !== product.value.id);
toast.add({severity: 'error', summary: 'Product Deleted', detail: product.value.name, life: 3000});
selectedProduct.value = null;
};
const formatCurrency = (value) => {
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
};
<\/script>
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onRowContextMenu(event) {
this.$refs.cm.show(event.originalEvent);
},
viewProduct(product) {
this.$toast.add({ severity: 'info', summary: 'Product Selected', detail: product.name, life: 3000 });
},
deleteProduct(product) {
this.products = this.products.filter((p) => p.id !== product.id);
this.$toast.add({ severity: 'error', summary: 'Product Deleted', detail: product.name, life: 3000 });
this.selectedProduct = null;
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
}
}
};
</script>

View file

@ -0,0 +1,114 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Columns can be created programmatically.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
columns: null,
code: {
basic: `
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
columns: null
};
},
created() {
this.columns = [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
];
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const columns = [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
];
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
created() {
this.columns = [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
];
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

122
doc/datatable/ExportDoc.vue Normal file
View file

@ -0,0 +1,122 @@
<template>
<DocSectionText v-bind="$attrs">
<p>DataTable can export its data to CSV format.</p>
</DocSectionText>
<div class="card">
<DataTable ref="dt" :value="products" tableStyle="min-width: 50rem">
<template #header>
<div style="text-align: left">
<Button icon="pi pi-external-link" label="Export" @click="exportCSV($event)" />
</div>
</template>
<Column field="code" header="Code" exportHeader="Product Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" ref="dt" tableStyle="min-width: 50rem">
<template #header>
<div style="text-align: left">
<Button icon="pi pi-external-link" label="Export" @click="exportCSV($event)" />
</div>
</template>
<Column field="code" header="Code" exportHeader="Product Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div>
<DataTable :value="products" ref="dt" tableStyle="min-width: 50rem">
<template #header>
<div style="text-align: left">
<Button icon="pi pi-external-link" label="Export" @click="exportCSV($event)" />
</div>
</template>
<Column field="code" header="Code" exportHeader="Product Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
}
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
exportCSV() {
this.$refs.dt.exportCSV();
}
}
}
<\\/script>
`,
composition: `
<template>
<div>
<DataTable :value="products" ref="dt" tableStyle="min-width: 50rem">
<template #header>
<div style="text-align: left">
<Button icon="pi pi-external-link" label="Export" @click="exportCSV($event)" />
</div>
</template>
<Column field="code" header="Code" exportHeader="Product Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const dt = ref();
const products = ref();
const exportCSV = () => {
dt.value.exportCSV();
};
<\\/script>
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
exportCSV() {
this.$refs.dt.exportCSV();
}
}
};
</script>

View file

@ -0,0 +1,102 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Enabling <i>showGridlines</i> displays borders between cells.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,17 @@
<template>
<DocSectionText v-bind="$attrs" />
<DocSectionCode :code="code" hideToggleCode import hideCodeSandbox hideStackBlitz />
</template>
<script>
export default {
data() {
return {
code: {
basic: `import DataTable from 'primevue/datatable';
import Column from 'primevue/column';`
}
};
}
};
</script>

View file

@ -0,0 +1,427 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Lazy mode is handy to deal with large datasets, instead of loading the entire data, small chunks of data is loaded by invoking corresponding callbacks everytime <i>paging</i>, <i>sorting</i> and <i>filtering</i> occurs. Sample below
imitates lazy loading data from a remote datasource using an in-memory list and timeouts to mimic network connection.
</p>
<p>
Enabling the <i>lazy</i> property and assigning the logical number of rows to <i>totalRecords</i> by doing a projection query are the key elements of the implementation so that paginator displays the UI assuming there are actually records
of totalRecords size although in reality they are not present on page, only the records that are displayed on the current page exist.
</p>
<p>Note that, the implementation of <i>checkbox selection</i> in lazy mode needs to be handled manually as in this example since the DataTable cannot know about the whole dataset.</p>
</DocSectionText>
<div class="card p-fluid">
<DataTable
ref="dt"
v-model:filters="filters"
v-model:selection="selectedCustomers"
:value="customers"
lazy
paginator
:rows="10"
dataKey="id"
:totalRecords="totalRecords"
:loading="loading"
@page="onPage($event)"
@sort="onSort($event)"
@filter="onFilter($event)"
filterDisplay="row"
:globalFilterFields="['name', 'country.name', 'company', 'representative.name']"
:selectAll="selectAll"
@select-all-change="onSelectAllChange"
@row-select="onRowSelect"
@row-unselect="onRowUnselect"
tableStyle="min-width: 75rem"
>
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="name" header="Name" filterMatchMode="startsWith" sortable>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search" />
</template>
</Column>
<Column field="country.name" header="Country" filterField="country.name" filterMatchMode="contains" sortable>
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${data.country.code}`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search" />
</template>
</Column>
<Column field="company" header="Company" filterMatchMode="contains" sortable>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search" />
</template>
</Column>
<Column field="representative.name" header="Representative" filterField="representative.name" sortable>
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${data.representative.image}`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search" />
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
loading: false,
totalRecords: 0,
customers: null,
selectedCustomers: null,
selectAll: false,
filters: {
name: { value: '', matchMode: 'contains' },
'country.name': { value: '', matchMode: 'contains' },
company: { value: '', matchMode: 'contains' },
'representative.name': { value: '', matchMode: 'contains' }
},
lazyParams: {},
columns: [
{ field: 'name', header: 'Name' },
{ field: 'country.name', header: 'Country' },
{ field: 'company', header: 'Company' },
{ field: 'representative.name', header: 'Representative' }
],
code: {
basic: `
<DataTable :value="customers" lazy paginator :rows="10" v-model:filters="filters" ref="dt" dataKey="id"
:totalRecords="totalRecords" :loading="loading" @page="onPage($event)" @sort="onSort($event)" @filter="onFilter($event)" filterDisplay="row"
:globalFilterFields="['name','country.name', 'company', 'representative.name']"
v-model:selection="selectedCustomers" :selectAll="selectAll" @select-all-change="onSelectAllChange" @row-select="onRowSelect" @row-unselect="onRowUnselect" tableStyle="min-width: 75rem">
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="name" header="Name" filterMatchMode="startsWith" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="country.name" header="Country" filterField="country.name" filterMatchMode="contains" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="company" header="Company" filterMatchMode="contains" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="representative.name" header="Representative" filterField="representative.name" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card p-fluid">
<DataTable :value="customers" lazy paginator :rows="10" v-model:filters="filters" ref="dt" dataKey="id"
:totalRecords="totalRecords" :loading="loading" @page="onPage($event)" @sort="onSort($event)" @filter="onFilter($event)" filterDisplay="row"
:globalFilterFields="['name','country.name', 'company', 'representative.name']"
v-model:selection="selectedCustomers" :selectAll="selectAll" @select-all-change="onSelectAllChange" @row-select="onRowSelect" @row-unselect="onRowUnselect" tableStyle="min-width: 75rem">
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="name" header="Name" filterMatchMode="startsWith" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="country.name" header="Country" filterField="country.name" filterMatchMode="contains" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="company" header="Company" filterMatchMode="contains" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="representative.name" header="Representative" filterField="representative.name" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import {CustomerService} from '@/service/CustomerService';
export default {
data() {
return {
loading: false,
totalRecords: 0,
customers: null,
selectedCustomers: null,
selectAll: false,
filters: {
'name': {value: '', matchMode: 'contains'},
'country.name': {value: '', matchMode: 'contains'},
'company': {value: '', matchMode: 'contains'},
'representative.name': {value: '', matchMode: 'contains'},
},
lazyParams: {},
columns: [
{field: 'name', header: 'Name'},
{field: 'country.name', header: 'Country'},
{field: 'company', header: 'Company'},
{field: 'representative.name', header: 'Representative'}
]
}
},
customerService: null,
created() {
this.customerService = new CustomerService();
},
mounted() {
this.loading = true;
this.lazyParams = {
first: 0,
rows: this.$refs.dt.rows,
sortField: null,
sortOrder: null,
filters: this.filters
};
this.loadLazyData();
},
methods: {
loadLazyData() {
this.loading = true;
setTimeout(() => {
CustomerService.getCustomers({ lazyEvent: JSON.stringify(this.lazyParams) }).then((data) => {
this.customers = data.customers;
this.totalRecords = data.totalRecords;
this.loading = false;
});
}, Math.random() * 1000 + 250);
},
onPage(event) {
this.lazyParams = event;
this.loadLazyData();
},
onSort(event) {
this.lazyParams = event;
this.loadLazyData();
},
onFilter() {
this.lazyParams.filters = this.filters;
this.loadLazyData();
},
onSelectAllChange(event) {
const selectAll = event.checked;
if (selectAll) {
this.customerService.getCustomers().then(data => {
this.selectAll = true;
this.selectedCustomers = data.customers;
});
}
else {
this.selectAll = false;
this.selectedCustomers = [];
}
},
onRowSelect() {
this.selectAll = this.selectedCustomers.length === this.totalRecords
},
onRowUnselect() {
this.selectAll = false;
}
}
}
<\/script>
`,
composition: `
<template>
<div class="card p-fluid">
<DataTable :value="customers" lazy paginator :rows="10" v-model:filters="filters" ref="dt" dataKey="id"
:totalRecords="totalRecords" :loading="loading" @page="onPage($event)" @sort="onSort($event)" @filter="onFilter($event)" filterDisplay="row"
:globalFilterFields="['name','country.name', 'company', 'representative.name']"
v-model:selection="selectedCustomers" :selectAll="selectAll" @select-all-change="onSelectAllChange" @row-select="onRowSelect" @row-unselect="onRowUnselect" tableStyle="min-width: 75rem">
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="name" header="Name" filterMatchMode="startsWith" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="country.name" header="Country" filterField="country.name" filterMatchMode="contains" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="company" header="Company" filterMatchMode="contains" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
<Column field="representative.name" header="Representative" filterField="representative.name" sortable>
<template #filter="{filterModel,filterCallback}">
<InputText type="text" v-model="filterModel.value" @keydown.enter="filterCallback()" class="p-column-filter" placeholder="Search"/>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import {CustomerService} from '@/service/CustomerService';
onMounted(() => {
loading.value = true;
lazyParams.value = {
first: 0,
rows: dt.value.rows,
sortField: null,
sortOrder: null,
filters: filters.value
};
loadLazyData();
});
const dt = ref();
const loading = ref(false);
const totalRecords = ref(0);
const customers = ref();
const selectedCustomers = ref();
const selectAll = ref(false);
const customerService = ref(new CustomerService());
const filters = ref({
'name': {value: '', matchMode: 'contains'},
'country.name': {value: '', matchMode: 'contains'},
'company': {value: '', matchMode: 'contains'},
'representative.name': {value: '', matchMode: 'contains'},
});
const lazyParams = ref({});
const columns = ref([
{field: 'name', header: 'Name'},
{field: 'country.name', header: 'Country'},
{field: 'company', header: 'Company'},
{field: 'representative.name', header: 'Representative'}
]);
const loadLazyData = () => {
loading.value = true;
setTimeout(() => {
CustomerService.getCustomers({ lazyEvent: JSON.stringify(this.lazyParams) }).then((data) => {
this.customers = data.customers;
this.totalRecords = data.totalRecords;
this.loading = false;
});
}, Math.random() * 1000 + 250);
};
const onPage = (event) => {
lazyParams.value = event;
loadLazyData();
};
const onSort = (event) => {
lazyParams.value = event;
loadLazyData();
};
const onFilter = () => {
lazyParams.value.filters = filters.value ;
loadLazyData();
};
const onSelectAllChange = (event) => {
const selectAll = event.checked;
if (selectAll) {
customerService.value.getCustomers().then(data => {
selectAll.value = true;
selectedCustomers.value = data.customers;
});
}
else {
selectAll.value = false;
selectedCustomers.value = [];
}
};
const onRowSelect = () => {
selectAll.value = selectedCustomers.value.length === totalRecords.value;
};
const onRowUnselect = () => {
selectAll.value = false;
};
<\/script>
`
}
};
},
mounted() {
this.loading = true;
this.lazyParams = {
first: 0,
rows: this.$refs.dt.rows,
sortField: null,
sortOrder: null,
filters: this.filters
};
this.loadLazyData();
},
methods: {
loadLazyData() {
this.loading = true;
setTimeout(() => {
CustomerService.getCustomers({ lazyEvent: JSON.stringify(this.lazyParams) }).then((data) => {
this.customers = data.customers;
this.totalRecords = data.totalRecords;
this.loading = false;
});
}, Math.random() * 1000 + 250);
},
onPage(event) {
this.lazyParams = event;
this.loadLazyData();
},
onSort(event) {
this.lazyParams = event;
this.loadLazyData();
},
onFilter() {
this.lazyParams.filters = this.filters;
this.loadLazyData();
},
onSelectAllChange(event) {
const selectAll = event.checked;
if (selectAll) {
this.customerService.getCustomers().then((data) => {
this.selectAll = true;
this.selectedCustomers = data.customers;
});
} else {
this.selectAll = false;
this.selectedCustomers = [];
}
},
onRowSelect() {
this.selectAll = this.selectedCustomers.length === this.totalRecords;
},
onRowUnselect() {
this.selectAll = false;
}
}
};
</script>

View file

@ -0,0 +1,139 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Order of the columns and rows can be changed using drag and drop. Column reordering is configured by adding <i>reorderableColumns</i> property.</p>
<p>
Similarly, adding <i>rowReorder</i> property to a column enables draggable rows. For the drag handle a column needs to have <i>rowReorder</i> property and table needs to have <i>row-reorder</i> event is required to control the state of
the rows after reorder completes.
</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" reorderableColumns @column-reorder="onColReorder" @row-reorder="onRowReorder" tableStyle="min-width: 50rem">
<Column rowReorder headerStyle="width: 3rem" :reorderableColumn="false" />
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
columns: null,
products: null,
code: {
basic: `
<DataTable :value="products" :reorderableColumns="true" @columnReorder="onColReorder" @rowReorder="onRowReorder" tableStyle="min-width: 50rem">
<Column rowReorder headerStyle="width: 3rem" :reorderableColumn="false" />
<Column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field"></Column>
</DataTable>`,
options: `
<template>
<div>
<DataTable :value="products" :reorderableColumns="true" @columnReorder="onColReorder" @rowReorder="onRowReorder" tableStyle="min-width: 50rem">
<Column rowReorder headerStyle="width: 3rem" :reorderableColumn="false" />
<Column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field"></Column>
</DataTable>
<Toast />
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
columns: null,
products: null
}
},
created() {
this.columns = [
{field: 'code', header: 'Code'},
{field: 'name', header: 'Name'},
{field: 'category', header: 'Category'},
{field: 'quantity', header: 'Quantity'}
];
},
mounted() {
ProductService.getProductsMini().then(data => this.products = data);
},
methods: {
onColReorder() {
this.$toast.add({severity:'success', summary: 'Column Reordered', life: 3000});
},
onRowReorder(event) {
this.products = event.value;
this.$toast.add({severity:'success', summary: 'Rows Reordered', life: 3000});
}
}
}
<\/script>
`,
composition: `
<template>
<div>
<DataTable :value="products" :reorderableColumns="true" @columnReorder="onColReorder" @rowReorder="onRowReorder" tableStyle="min-width: 50rem">
<Column rowReorder headerStyle="width: 3rem" :reorderableColumn="false" />
<Column v-for="col of columns" :field="col.field" :header="col.header" :key="col.field"></Column>
</DataTable>
<Toast />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useToast } from 'primevue/usetoast';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then(data => products.value = data);
});
const toast = useToast();
const columns = ref([
{field: 'code', header: 'Code'},
{field: 'name', header: 'Name'},
{field: 'category', header: 'Category'},
{field: 'quantity', header: 'Quantity'}
]);
const products = ref();
const onColReorder = () => {
toast.add({severity:'success', summary: 'Column Reordered', life: 3000});
};
const onRowReorder = (event) => {
products.value = event.value;
toast.add({severity:'success', summary: 'Rows Reordered', life: 3000});
};
<\/script>
`
}
};
},
created() {
this.columns = [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
];
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onColReorder() {
this.$toast.add({ severity: 'success', summary: 'Column Reordered', life: 3000 });
},
onRowReorder(event) {
this.products = event.value;
this.$toast.add({ severity: 'success', summary: 'Rows Reordered', life: 3000 });
}
}
};
</script>

View file

@ -0,0 +1,480 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Row expansion is controlled with <i>expandedRows</i> property. The column that has the expander element requires <i>expander</i> property to be enabled. Optional <i>rowExpand</i> and <i>rowCollapse</i>
events are available as callbacks.
</p>
<p>
Expanded rows can either be an array of row data or when <i>dataKey</i> is present, an object whose keys are strings referring to the identifier of the row data and values are booleans to represent the expansion state e.g.
<i>&#123;'1004': true&#125;</i>. The <i>dataKey</i> alternative is more performant for large amounts of data.
</p>
</DocSectionText>
<div class="card">
<DataTable v-model:expandedRows="expandedRows" :value="products" dataKey="id" @rowExpand="onRowExpand" @rowCollapse="onRowCollapse" tableStyle="min-width: 60rem">
<template #header>
<div class="flex flex-wrap justify-content-end gap-2">
<Button class="p-button-text" icon="pi pi-plus" label="Expand All" @click="expandAll" />
<Button class="p-button-text" icon="pi pi-minus" label="Collapse All" @click="collapseAll" />
</div>
</template>
<Column expander style="width: 5rem" />
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="shadow-4" width="64" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly :cancel="false" />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #expansion="slotProps">
<div class="p-3">
<h5>Orders for {{ slotProps.data.name }}</h5>
<DataTable :value="slotProps.data.orders">
<Column field="id" header="Id" sortable></Column>
<Column field="customer" header="Customer" sortable></Column>
<Column field="date" header="Date" sortable></Column>
<Column field="amount" header="Amount" sortable>
<template #body="slotProps">
{{ formatCurrency(slotProps.data.amount) }}
</template>
</Column>
<Column field="status" header="Status" sortable>
<template #body="slotProps">
<Tag :value="slotProps.data.status.toLowerCase()" :severity="getOrderSeverity(slotProps.data)" />
</template>
</Column>
<Column headerStyle="width:4rem">
<template #body>
<Button icon="pi pi-search" />
</template>
</Column>
</DataTable>
</div>
</template>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
expandedRows: [],
code: {
basic: `
<DataTable v-model:expandedRows="expandedRows" :value="products" dataKey="id"
@rowExpand="onRowExpand" @rowCollapse="onRowCollapse" tableStyle="min-width: 60rem">
<template #header>
<div class="flex flex-wrap justify-content-end gap-2">
<Button class="p-button-text" icon="pi pi-plus" label="Expand All" @click="expandAll" />
<Button class="p-button-text" icon="pi pi-minus" label="Collapse All" @click="collapseAll" />
</div>
</template>
<Column expander style="width: 5rem" />
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-4" width="64" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly :cancel="false" />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #expansion="slotProps">
<div class="p-3">
<h5>Orders for {{ slotProps.data.name }}</h5>
<DataTable :value="slotProps.data.orders">
<Column field="id" header="Id" sortable></Column>
<Column field="customer" header="Customer" sortable></Column>
<Column field="date" header="Date" sortable></Column>
<Column field="amount" header="Amount" sortable>
<template #body="slotProps">
{{ formatCurrency(slotProps.data.amount) }}
</template>
</Column>
<Column field="status" header="Status" sortable>
<template #body="slotProps">
<Tag :value="slotProps.data.status.toLowerCase()" :severity="getOrderSeverity(slotProps.data)" />
</template>
</Column>
<Column headerStyle="width:4rem">
<template #body>
<Button icon="pi pi-search" />
</template>
</Column>
</DataTable>
</div>
</template>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:expandedRows="expandedRows" :value="products" dataKey="id"
@rowExpand="onRowExpand" @rowCollapse="onRowCollapse" tableStyle="min-width: 60rem">
<template #header>
<div class="flex flex-wrap justify-content-end gap-2">
<Button class="p-button-text" icon="pi pi-plus" label="Expand All" @click="expandAll" />
<Button class="p-button-text" icon="pi pi-minus" label="Collapse All" @click="collapseAll" />
</div>
</template>
<Column expander style="width: 5rem" />
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-4" width="64" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly :cancel="false" />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #expansion="slotProps">
<div class="p-3">
<h5>Orders for {{ slotProps.data.name }}</h5>
<DataTable :value="slotProps.data.orders" >
<Column field="id" header="Id" sortable></Column>
<Column field="customer" header="Customer" sortable></Column>
<Column field="date" header="Date" sortable></Column>
<Column field="amount" header="Amount" sortable>
<template #body="slotProps">
{{ formatCurrency(slotProps.data.amount) }}
</template>
</Column>
<Column field="status" header="Status" sortable>
<template #body="slotProps">
<Tag :value="slotProps.data.status.toLowerCase()" :severity="getOrderSeverity(slotProps.data)" />
</template>
</Column>
<Column headerStyle="width:4rem">
<template #body>
<Button icon="pi pi-search" />
</template>
</Column>
</DataTable>
</div>
</template>
</DataTable>
<Toast />
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
expandedRows: []
};
},
mounted() {
ProductService.getProductsWithOrdersSmall().then((data) => (this.products = data));
},
methods: {
onRowExpand(event) {
this.$toast.add({ severity: 'info', summary: 'Product Expanded', detail: event.data.name, life: 3000 });
},
onRowCollapse(event) {
this.$toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 });
},
expandAll() {
this.expandedRows = this.products.filter((p) => p.id);
},
collapseAll() {
this.expandedRows = null;
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
getSeverity(product) {
switch (product.inventoryStatus) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
},
getOrderSeverity(order) {
switch (order.status) {
case 'DELIVERED':
return 'success';
case 'CANCELLED':
return 'danger';
case 'PENDING':
return 'warning';
case 'RETURNED':
return 'info';
default:
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:expandedRows="expandedRows" :value="products" dataKey="id"
@rowExpand="onRowExpand" @rowCollapse="onRowCollapse" tableStyle="min-width: 60rem">
<template #header>
<div class="flex flex-wrap justify-content-end gap-2">
<Button class="p-button-text" icon="pi pi-plus" label="Expand All" @click="expandAll" />
<Button class="p-button-text" icon="pi pi-minus" label="Collapse All" @click="collapseAll" />
</div>
</template>
<Column expander style="width: 5rem" />
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-4" width="64" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly :cancel="false" />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #expansion="slotProps">
<div class="p-3">
<h5>Orders for {{ slotProps.data.name }}</h5>
<DataTable :value="slotProps.data.orders">
<Column field="id" header="Id" sortable></Column>
<Column field="customer" header="Customer" sortable></Column>
<Column field="date" header="Date" sortable></Column>
<Column field="amount" header="Amount" sortable>
<template #body="slotProps">
{{ formatCurrency(slotProps.data.amount) }}
</template>
</Column>
<Column field="status" header="Status" sortable>
<template #body="slotProps">
<Tag :value="slotProps.data.status.toLowerCase()" :severity="getOrderSeverity(slotProps.data)" />
</template>
</Column>
<Column headerStyle="width:4rem">
<template #body>
<Button icon="pi pi-search" />
</template>
</Column>
</DataTable>
</div>
</template>
</DataTable>
<Toast />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useToast } from 'primevue/usetoast';
import { ProductService } from '@/service/ProductService';
const products = ref();
const expandedRows = ref([]);
const toast = useToast();
onMounted(() => {
ProductService.getProductsWithOrdersSmall().then((data) => (products.value = data));
});
const onRowExpand = (event) => {
toast.add({ severity: 'info', summary: 'Product Expanded', detail: event.data.name, life: 3000 });
};
const onRowCollapse = (event) => {
toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 });
};
const expandAll = () => {
expandedRows.value = products.value.filter((p) => p.id);
};
const collapseAll = () => {
expandedRows.value = null;
};
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const getSeverity = (product) => {
switch (product.inventoryStatus) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
};
const getOrderSeverity = (order) => {
switch (order.status) {
case 'DELIVERED':
return 'success';
case 'CANCELLED':
return 'danger';
case 'PENDING':
return 'warning';
case 'RETURNED':
return 'info';
default:
return null;
}
};
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5,
orders: [
{
id: '1000-0',
productCode: 'f230fh0g3',
date: '2020-09-13',
amount: 65,
quantity: 1,
customer: 'David James',
status: 'PENDING'
},
...
]
},
...
`
}
};
},
mounted() {
ProductService.getProductsWithOrdersSmall().then((data) => (this.products = data));
},
methods: {
onRowExpand(event) {
this.$toast.add({ severity: 'info', summary: 'Product Expanded', detail: event.data.name, life: 3000 });
},
onRowCollapse(event) {
this.$toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 });
},
expandAll() {
this.expandedRows = this.products.filter((p) => p.id);
},
collapseAll() {
this.expandedRows = null;
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
getSeverity(product) {
switch (product.inventoryStatus) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
},
getOrderSeverity(order) {
switch (order.status) {
case 'DELIVERED':
return 'success';
case 'CANCELLED':
return 'danger';
case 'PENDING':
return 'warning';
case 'RETURNED':
return 'info';
default:
return null;
}
}
}
};
</script>

130
doc/datatable/SizeDoc.vue Normal file
View file

@ -0,0 +1,130 @@
<template>
<DocSectionText v-bind="$attrs">
<p>In addition to a regular table, alternatives with alternative sizes are available.</p>
</DocSectionText>
<div class="card">
<div class="flex justify-content-center mb-4">
<SelectButton v-model="size" :options="sizeOptions" optionLabel="label" dataKey="label" />
</div>
<DataTable :value="products" :class="`p-datatable-${size.class}`" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
size: { label: 'Normal', value: 'normal' },
sizeOptions: [
{ label: 'Small', value: 'small', class: 'sm' },
{ label: 'Normal', value: 'normal' },
{ label: 'Large', value: 'large', class: 'lg' }
],
code: {
basic: `
<SelectButton v-model="size" :options="sizeOptions" optionLabel="label" dataKey="label" />
<DataTable :value="products" :class="\`p-datatable-\${size.class}\`" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<div class="flex justify-content-center mb-4">
<SelectButton v-model="size" :options="sizeOptions" optionLabel="label" dataKey="label" />
</div>
<DataTable :value="products" :class="\`p-datatable-\${size.class}\`" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
size: { label: 'Normal', value: 'normal' },
sizeOptions: [
{ label: 'Small', value: 'small', class: 'sm' },
{ label: 'Normal', value: 'normal' },
{ label: 'Large', value: 'large', class: 'lg' }
]
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<div class="flex justify-content-center mb-4">
<SelectButton v-model="size" :options="sizeOptions" optionLabel="label" dataKey="label" />
</div>
<DataTable :value="products" :class="\`p-datatable-\${size.class}\`" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const size = ref({ label: 'Normal', value: 'normal' });
const sizeOptions = ref([
{ label: 'Small', value: 'small', class: 'sm' },
{ label: 'Normal', value: 'normal' },
{ label: 'Large', value: 'large', class: 'lg' }
]);
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,466 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Stateful table allows keeping the state such as page, sort and filtering either at local storage or session storage so that when the page is visited again, table would render the data using the last settings.</p>
<p>
Change the state of the table e.g paginate, navigate away and then return to this table again to test this feature, the setting is set as <i>session</i> with the <i>stateStorage</i> property so that Table retains the state until the
browser is closed. Other alternative is <i>local</i> referring to <i>localStorage</i> for an extended lifetime.
</p>
</DocSectionText>
<div class="card">
<DataTable
v-model:filters="filters"
v-model:selection="selectedCustomer"
:value="customers"
stateStorage="session"
stateKey="dt-state-demo-session"
paginator
:rows="5"
filterDisplay="menu"
selectionMode="single"
dataKey="id"
:globalFilterFields="['name', 'country.name', 'representative.name', 'status']"
tableStyle="min-width: 50rem"
>
<template #header>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Global Search" />
</span>
</template>
<Column field="name" header="Name" sortable style="width: 25%">
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" sortable sortField="country.name" filterField="country.name" filterMatchMode="contains" style="width: 25%">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${data.country.code}`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Representative" sortable filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="width: 25%">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${data.representative.image}`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${slotProps.option.image}`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="status" header="Status" sortable filterMatchMode="equals" style="width: 25%">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<template #empty> No customers found. </template>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
export default {
data() {
return {
customers: null,
selectedCustomer: null,
filters: null,
representatives: [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
],
statuses: ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal'],
code: {
basic: `
<DataTable v-model:filters="filters" v-model:selection="selectedCustomer" :value="customers"
stateStorage="session" stateKey="dt-state-demo-session" paginator :rows="5" filterDisplay="menu"
selectionMode="single" dataKey="id" :globalFilterFields="['name', 'country.name', 'representative.name', 'status']" tableStyle="min-width: 50rem">
<template #header>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Global Search" />
</span>
</template>
<Column field="name" header="Name" sortable style="width: 25%">
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" sortable sortField="country.name" filterField="country.name" filterMatchMode="contains" style="width: 25%">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Representative" sortable filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="width: 25%">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="status" header="Status" sortable filterMatchMode="equals" style="width: 25%">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<template #empty> No customers found. </template>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:filters="filters" v-model:selection="selectedCustomer" :value="customers"
stateStorage="session" stateKey="dt-state-demo-session" paginator :rows="5" filterDisplay="menu"
selectionMode="single" dataKey="id" :globalFilterFields="['name', 'country.name', 'representative.name', 'status']" tableStyle="min-width: 50rem">
<template #header>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Global Search" />
</span>
</template>
<Column field="name" header="Name" sortable style="width: 25%">
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" sortable sortField="country.name" filterField="country.name" filterMatchMode="contains" style="width: 25%">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Representative" sortable filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="width: 25%">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="status" header="Status" sortable filterMatchMode="equals" style="width: 25%">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<template #empty> No customers found. </template>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
export default {
data() {
return {
customers: null,
selectedCustomer: null,
filters: null,
representatives: [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
],
statuses: ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal']
};
},
created() {
this.initFilters();
},
mounted() {
CustomerService.getCustomersSmall().then((data) => (this.customers = data));
},
methods: {
initFilters() {
this.filters = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] }
};
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:filters="filters" v-model:selection="selectedCustomer" :value="customers"
stateStorage="session" stateKey="dt-state-demo-session" paginator :rows="5" filterDisplay="menu"
selectionMode="single" dataKey="id" :globalFilterFields="['name', 'country.name', 'representative.name', 'status']" tableStyle="min-width: 50rem">
<template #header>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Global Search" />
</span>
</template>
<Column field="name" header="Name" sortable style="width: 25%">
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" sortable sortField="country.name" filterField="country.name" filterMatchMode="contains" style="width: 25%">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Representative" sortable filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="width: 25%">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="status" header="Status" sortable filterMatchMode="equals" style="width: 25%">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<template #empty> No customers found. </template>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
const customers = ref();
const selectedCustomer = ref();
const filters = ref(
{
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] }
}
);
const representatives = ref([
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
]);
const statuses = ref(['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal']);
onMounted(() => {
CustomerService.getCustomersSmall().then((data) => (customers.value = data));
});
const getSeverity = (status) => {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
};
<\/script>`,
data: `
/* CustomerService */
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...`
}
};
},
created() {
this.initFilters();
},
mounted() {
CustomerService.getCustomersSmall().then((data) => (this.customers = data));
},
methods: {
initFilters() {
this.filters = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] }
};
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,102 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Alternating rows are displayed when <i>stripedRows</i> property is present.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" stripedRows tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" stripedRows tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" stripedRows tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" stripedRows tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,97 @@
<template>
<DocSectionText id="style" label="Style" v-bind="$attrs">
<p>Following is the list of structural style classes, for theming classes visit <NuxtLink to="/theming"> theming</NuxtLink> page.</p>
</DocSectionText>
<div class="doc-tablewrapper">
<table class="doc-table">
<thead>
<tr>
<th>Name</th>
<th>Element</th>
</tr>
</thead>
<tbody>
<tr>
<td>p-datatable</td>
<td>Container element.</td>
</tr>
<tr>
<td>p-datatable-scrollable</td>
<td>Container element when table is scrollable.</td>
</tr>
<tr>
<td>p-datatable-header</td>
<td>Header section.</td>
</tr>
<tr>
<td>p-datatable-footer</td>
<td>Footer section.</td>
</tr>
<tr>
<td>p-datatable-wrapper</td>
<td>Wrapper of table element.</td>
</tr>
<tr>
<td>p-datatable-table</td>
<td>Table element.</td>
</tr>
<tr>
<td>p-datatable-thead</td>
<td>Table thead element.</td>
</tr>
<tr>
<td>p-datatable-tbody</td>
<td>Table tbody element.</td>
</tr>
<tr>
<td>p-datatable-tfoot</td>
<td>Table tfoot element.</td>
</tr>
<tr>
<td>p-column-title</td>
<td>Title of a column.</td>
</tr>
<tr>
<td>p-sortable-column</td>
<td>Sortable column header.</td>
</tr>
<tr>
<td>p-frozen-column</td>
<td>Frozen column header.</td>
</tr>
<tr>
<td>p-rowgroup-header</td>
<td>Header of a rowgroup.</td>
</tr>
<tr>
<td>p-rowgroup-footer</td>
<td>Footer of a rowgroup.</td>
</tr>
<tr>
<td>p-datatable-row-expansion</td>
<td>Expanded row content.</td>
</tr>
<tr>
<td>p-row-toggler</td>
<td>Toggle element for row expansion.</td>
</tr>
<tr>
<td>p-datatable-emptymessage</td>
<td>Cell containing the empty message.</td>
</tr>
<tr>
<td>p-row-editor-init</td>
<td>Pencil button of row editor.</td>
</tr>
<tr>
<td>p-row-editor-init</td>
<td>Save button of row editor.</td>
</tr>
<tr>
<td>p-row-editor-init</td>
<td>Cancel button of row editor.</td>
</tr>
</tbody>
</table>
</div>
</template>

View file

@ -0,0 +1,260 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Custom content at <i>header</i> and <i>footer</i> sections are supported via templating.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<template #header>
<div class="flex flex-wrap align-items-center justify-content-between gap-2">
<span class="text-xl text-900 font-bold">Products</span>
<Button icon="pi pi-refresh" class="p-button-rounded p-button-raised" />
</div>
</template>
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="w-6rem shadow-2 border-round" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly :cancel="false" />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #footer> In total there are {{ products ? products.length : 0 }} products. </template>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" tableStyle="min-width: 50rem">
<template #header>
<div class="flex flex-wrap align-items-center justify-content-between gap-2">
<span class="text-xl text-900 font-bold">Products</span>
<Button icon="pi pi-refresh" class="p-button-rounded p-button-raised" />
</div>
</template>
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-6rem shadow-2 border-round" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly :cancel="false" />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #footer> In total there are {{ products ? products.length : 0 }} products. </template>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<template #header>
<div class="flex flex-wrap align-items-center justify-content-between gap-2">
<span class="text-xl text-900 font-bold">Products</span>
<Button icon="pi pi-refresh" class="p-button-rounded p-button-raised" />
</div>
</template>
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-6rem shadow-2 border-round" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly :cancel="false" />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #footer> In total there are {{ products ? products.length : 0 }} products. </template>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
getSeverity(product) {
switch (product.inventoryStatus) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<template #header>
<div class="flex flex-wrap align-items-center justify-content-between gap-2">
<span class="text-xl text-900 font-bold">Products</span>
<Button icon="pi pi-refresh" class="p-button-rounded p-button-raised" />
</div>
</template>
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="w-6rem shadow-2 border-round" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly :cancel="false" />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #footer> In total there are {{ products ? products.length : 0 }} products. </template>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const getSeverity = (product) => {
switch (product.inventoryStatus) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
};
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
getSeverity(product) {
switch (product.inventoryStatus) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,86 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Setting <i>columnResizeMode</i> as <i>expand</i> changes the table width as well.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" resizableColumns columnResizeMode="expand" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" resizableColumns columnResizeMode="expand" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" resizableColumns columnResizeMode="expand" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsSmall().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" resizableColumns columnResizeMode="expand" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
productService.value.getProductsSmall().then(data => products.value = data);
});
const products = ref();
<\/script>`
}
};
},
mounted() {
ProductService.getProductsSmall().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,89 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Columns can be resized with drag and drop when <i>resizableColumns</i> is enabled. Default resize mode is <i>fit</i>
that does not change the overall table width.
</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" resizableColumns columnResizeMode="fit" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" resizableColumns columnResizeMode="fit" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" resizableColumns columnResizeMode="fit" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsSmall().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" resizableColumns columnResizeMode="fit" showGridlines tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
productService.value.getProductsSmall().then(data => products.value = data);
});
const products = ref();
<\/script>`
}
};
},
mounted() {
ProductService.getProductsSmall().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,262 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Cell editing is enabled by setting <i>editMode</i> as <i>cell</i>, defining input elements with <i>editor</i> templating of a Column and implementing <i>cell-edit-complete</i> to update the state.</p>
</DocSectionText>
<div class="card p-fluid">
<DataTable :value="products" editMode="cell" @cell-edit-complete="onCellEditComplete" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%">
<template #body="{ data, field }">
{{ field === 'price' ? formatCurrency(data[field]) : data[field] }}
</template>
<template #editor="{ data, field }">
<template v-if="field !== 'price'">
<InputText v-model="data[field]" autofocus />
</template>
<template v-else>
<InputNumber v-model="data[field]" mode="currency" currency="USD" locale="en-US" autofocus />
</template>
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
columns: [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'quantity', header: 'Quantity' },
{ field: 'price', header: 'Price' }
],
code: {
basic: `
<DataTable :value="products" editMode="cell" @cell-edit-complete="onCellEditComplete" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%">
<template #body="{ data, field }">
{{ field === 'price' ? formatCurrency(data[field]) : data[field] }}
</template>
<template #editor="{ data, field }">
<template v-if="field !== 'price'">
<InputText v-model="data[field]" autofocus />
</template>
<template v-else>
<InputNumber v-model="data[field]" mode="currency" currency="USD" locale="en-US" autofocus />
</template>
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card p-fluid">
<DataTable :value="products" editMode="cell" @cell-edit-complete="onCellEditComplete" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%">
<template #body="{ data, field }">
{{ field === 'price' ? formatCurrency(data[field]) : data[field] }}
</template>
<template #editor="{ data, field }">
<template v-if="field !== 'price'">
<InputText v-model="data[field]" autofocus />
</template>
<template v-else>
<InputNumber v-model="data[field]" mode="currency" currency="USD" locale="en-US" autofocus />
</template>
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
columns: [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'quantity', header: 'Quantity' },
{ field: 'price', header: 'Price' }
]
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
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;
},
formatCurrency(value) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
}
}
};
<\/script>
`,
composition: `
<template>
<div class="card p-fluid">
<DataTable :value="products" editMode="cell" @cell-edit-complete="onCellEditComplete" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%">
<template #body="{ data, field }">
{{ field === 'price' ? formatCurrency(data[field]) : data[field] }}
</template>
<template #editor="{ data, field }">
<template v-if="field !== 'price'">
<InputText v-model="data[field]" autofocus />
</template>
<template v-else>
<InputNumber v-model="data[field]" mode="currency" currency="USD" locale="en-US" autofocus />
</template>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
const products = ref();
const columns = ref([
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'quantity', header: 'Quantity' },
{ field: 'price', header: 'Price' }
]);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
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 formatCurrency = (value) => {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
}
<\/script>
`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
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;
},
formatCurrency(value) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
}
}
};
</script>

View file

@ -0,0 +1,212 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Cell Editing with Sorting and Filter</p>
</DocSectionText>
<div class="card p-fluid">
<DataTable v-model:filters="filters" :value="products" editMode="cell" @cell-edit-complete="onCellEditComplete" filterDisplay="row" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%" sortable filter>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" v-tooltip.top.focus="'Hit enter key to filter'" type="text" @keydown.enter="filterCallback()" class="p-column-filter" />
</template>
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
import { FilterMatchMode } from 'primevue/api';
export default {
data() {
return {
products: null,
columns: null,
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 }
},
code: {
basic: `
<DataTable v-model:filters="filters" :value="products" editMode="cell"
@cell-edit-complete="onCellEditComplete" filterDisplay="row" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%" sortable filter>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" v-tooltip.top.focus="'Hit enter key to filter'" type="text" @keydown.enter="filterCallback()" class="p-column-filter" />
</template>
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card p-fluid">
<DataTable v-model:filters="filters" :value="products" editMode="cell"
@cell-edit-complete="onCellEditComplete" filterDisplay="row" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%" sortable filter>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" v-tooltip.top.focus="'Hit enter key to filter'" type="text" @keydown.enter="filterCallback()" class="p-column-filter" />
</template>
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { FilterMatchMode } from 'primevue/api';
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
columns: null,
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 }
}
};
},
created() {
this.columns = [
{field: 'code', header: 'Code'},
{field: 'name', header: 'Name'},
{field: 'quantity', header: 'Quantity'},
{field: 'price', header: 'Price'}
];
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
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;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card p-fluid">
<DataTable v-model:filters="filters" :value="products" editMode="cell"
@cell-edit-complete="onCellEditComplete" filterDisplay="row" tableStyle="min-width: 50rem">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header" style="width: 25%" sortable filter>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" v-tooltip.top.focus="'Hit enter key to filter'" type="text" @keydown.enter="filterCallback()" class="p-column-filter" />
</template>
<template #editor="{ data, field }">
<InputText v-model="data[field]" autofocus />
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { FilterMatchMode } from 'primevue/api';
import { ProductService } from '@/service/ProductService';
const products = ref();
const columns = ref([
{field: 'code', header: 'Code'},
{field: 'name', header: 'Name'},
{field: 'quantity', header: 'Quantity'},
{field: 'price', header: 'Price'}
]);
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}
});
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
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;
}
};
<\/script>`
}
};
},
created() {
this.columns = [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'quantity', header: 'Quantity' },
{ field: 'price', header: 'Price' }
];
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
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;
}
}
}
};
</script>

View file

@ -0,0 +1,293 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Row editing is configured with setting <i>editMode</i> as <i>row</i> and defining <i>editingRows</i> with the v-model directive to hold the reference of the editing rows. Similarly with cell edit mode, defining input elements with
<i>editor</i> slot of a Column and implementing <i>row-edit-save</i> are necessary to update the state. The column to control the editing state should have <i>editor</i> templating applied.
</p>
</DocSectionText>
<div class="card p-fluid">
<DataTable v-model:editingRows="editingRows" :value="products" editMode="row" dataKey="id" @row-edit-save="onRowEditSave" tableStyle="min-width: 50rem">
<Column field="code" header="Code" style="width: 20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</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">
<Tag :value="slotProps.option.value" :severity="getStatusLabel(slotProps.option.value)" />
</template>
</Dropdown>
</template>
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" />
</template>
</Column>
<Column field="price" header="Price" style="width: 20%">
<template #body="{ data, field }">
{{ formatCurrency(data[field]) }}
</template>
<template #editor="{ data, field }">
<InputNumber v-model="data[field]" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column :rowEditor="true" style="width: 10%; min-width: 8rem" bodyStyle="text-align:center"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
editingRows: [],
statuses: [
{ label: 'In Stock', value: 'INSTOCK' },
{ label: 'Low Stock', value: 'LOWSTOCK' },
{ label: 'Out of Stock', value: 'OUTOFSTOCK' }
],
code: {
basic: `
<DataTable v-model:editingRows="editingRows" :value="products" editMode="row" dataKey="id"
@row-edit-save="onRowEditSave" tableStyle="min-width: 50rem">
<Column field="code" header="Code" style="width: 20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</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">
<Tag :value="slotProps.option.value" :severity="getStatusLabel(slotProps.option.value)" />
</template>
</Dropdown>
</template>
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" />
</template>
</Column>
<Column field="price" header="Price" style="width: 20%">
<template #body="{ data, field }">
{{ formatCurrency(data[field]) }}
</template>
<template #editor="{ data, field }">
<InputNumber v-model="data[field]" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column :rowEditor="true" style="width: 10%; min-width: 8rem" bodyStyle="text-align:center"></Column>
</DataTable>`,
options: `
<template>
<div class="card p-fluid">
<DataTable v-model:editingRows="editingRows" :value="products" editMode="row" dataKey="id"
@row-edit-save="onRowEditSave" tableStyle="min-width: 50rem">
<Column field="code" header="Code" style="width: 20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</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">
<Tag :value="slotProps.option.value" :severity="getStatusLabel(slotProps.option.value)" />
</template>
</Dropdown>
</template>
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" />
</template>
</Column>
<Column field="price" header="Price" style="width: 20%">
<template #body="{ data, field }">
{{ formatCurrency(data[field]) }}
</template>
<template #editor="{ data, field }">
<InputNumber v-model="data[field]" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column :rowEditor="true" style="width: 10%; min-width: 8rem" bodyStyle="text-align:center"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
editingRows: [],
statuses: [
{ label: 'In Stock', value: 'INSTOCK' },
{ label: 'Low Stock', value: 'LOWSTOCK' },
{ label: 'Out of Stock', value: 'OUTOFSTOCK' }
]
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onRowEditSave(event) {
let { newData, index } = event;
this.products[index] = newData;
},
getStatusLabel(status) {
switch (status) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
},
formatCurrency(value) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
}
}
};
<\/script>`,
composition: `
<template>
<div class="card p-fluid">
<DataTable v-model:editingRows="editingRows" :value="products" editMode="row" dataKey="id"
@row-edit-save="onRowEditSave" tableStyle="min-width: 50rem">
<Column field="code" header="Code" style="width: 20%">
<template #editor="{ data, field }">
<InputText v-model="data[field]" />
</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">
<Tag :value="slotProps.option.value" :severity="getStatusLabel(slotProps.option.value)" />
</template>
</Dropdown>
</template>
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" />
</template>
</Column>
<Column field="price" header="Price" style="width: 20%">
<template #body="{ data, field }">
{{ formatCurrency(data[field]) }}
</template>
<template #editor="{ data, field }">
<InputNumber v-model="data[field]" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column :rowEditor="true" style="width: 10%; min-width: 8rem" bodyStyle="text-align:center"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
const products = ref();
const editingRows = ref([]);
const statuses = ref([
{ label: 'In Stock', value: 'INSTOCK' },
{ label: 'Low Stock', value: 'LOWSTOCK' },
{ label: 'Out of Stock', value: 'OUTOFSTOCK' }
]);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const onRowEditSave = (event) => {
let { newData, index } = event;
products.value[index] = newData;
};
const getStatusLabel = (status) => {
switch (status) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
};
const formatCurrency = (value) => {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
}
<\/script>`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onRowEditSave(event) {
let { newData, index } = event;
this.products[index] = newData;
},
getStatusLabel(status) {
switch (status) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
},
formatCurrency(value) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
}
}
};
</script>

View file

@ -0,0 +1,722 @@
<template>
<DocSectionText v-bind="$attrs">
<p>When <i>filterDisplay</i> is set as <i>menu</i>, filtering is done via popups with support for multiple constraints and advanced templating.</p>
</DocSectionText>
<div class="card">
<DataTable v-model:filters="filters" :value="customers" paginator showGridlines :rows="10" dataKey="id" filterDisplay="menu" :loading="loading" :globalFilterFields="['name', 'country.name', 'representative.name', 'balance', 'status']">
<template #header>
<div class="flex justify-content-between">
<Button type="button" icon="pi pi-filter-slash" label="Clear" class="p-button-outlined" @click="clearFilter()" />
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<template #loading> Loading customers data. Please wait. </template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${data.country.code}`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
<template #filterclear="{ filterCallback }">
<Button type="button" icon="pi pi-times" @click="filterCallback()" class="p-button-secondary"></Button>
</template>
<template #filterapply="{ filterCallback }">
<Button type="button" icon="pi pi-check" @click="filterCallback()" class="p-button-success"></Button>
</template>
<template #filterfooter>
<div class="px-3 pt-0 pb-3 text-center">Customized Buttons</div>
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${data.representative.image}`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${slotProps.option.image}`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column header="Date" filterField="date" dataType="date" style="min-width: 10rem">
<template #body="{ data }">
{{ formatDate(data.date) }}
</template>
<template #filter="{ filterModel }">
<Calendar v-model="filterModel.value" dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />
</template>
</Column>
<Column header="Balance" filterField="balance" dataType="numeric" style="min-width: 10rem">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
<template #filter="{ filterModel }">
<InputNumber v-model="filterModel.value" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column header="Status" field="status" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="activity" header="Activity" :showFilterMatchModes="false" style="min-width: 12rem">
<template #body="{ data }">
<ProgressBar :value="data.activity" :showValue="false" style="height: 6px"></ProgressBar>
</template>
<template #filter="{ filterModel }">
<Slider v-model="filterModel.value" range class="m-3"></Slider>
<div class="flex align-items-center justify-content-between px-2">
<span>{{ filterModel.value ? filterModel.value[0] : 0 }}</span>
<span>{{ filterModel.value ? filterModel.value[1] : 100 }}</span>
</div>
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" bodyClass="text-center" style="min-width: 8rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500 ': data.verified, 'pi-times-circle text-red-500': !data.verified }"></i>
</template>
<template #filter="{ filterModel }">
<label for="verified-filter" class="font-bold"> Verified </label>
<TriStateCheckbox v-model="filterModel.value" inputId="verified-filter" />
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
export default {
data() {
return {
customers: null,
filters: null,
representatives: [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
],
statuses: ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal'],
loading: true,
code: {
basic: `
<DataTable v-model:filters="filters" :value="customers" paginator showGridlines :rows="10" dataKey="id"
filterDisplay="menu" :loading="loading" :globalFilterFields="['name', 'country.name', 'representative.name', 'balance', 'status']">
<template #header>
<div class="flex justify-content-between">
<Button type="button" icon="pi pi-filter-slash" label="Clear" class="p-button-outlined" @click="clearFilter()" />
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<template #loading> Loading customers data. Please wait. </template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
<template #filterclear="{ filterCallback }">
<Button type="button" icon="pi pi-times" @click="filterCallback()" class="p-button-secondary"></Button>
</template>
<template #filterapply="{ filterCallback }">
<Button type="button" icon="pi pi-check" @click="filterCallback()" class="p-button-success"></Button>
</template>
<template #filterfooter>
<div class="px-3 pt-0 pb-3 text-center">Customized Buttons</div>
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column header="Date" filterField="date" dataType="date" style="min-width: 10rem">
<template #body="{ data }">
{{ formatDate(data.date) }}
</template>
<template #filter="{ filterModel }">
<Calendar v-model="filterModel.value" dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />
</template>
</Column>
<Column header="Balance" filterField="balance" dataType="numeric" style="min-width: 10rem">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
<template #filter="{ filterModel }">
<InputNumber v-model="filterModel.value" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column header="Status" field="status" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="activity" header="Activity" :showFilterMatchModes="false" style="min-width: 12rem">
<template #body="{ data }">
<ProgressBar :value="data.activity" :showValue="false" style="height: 6px"></ProgressBar>
</template>
<template #filter="{ filterModel }">
<Slider v-model="filterModel.value" range class="m-3"></Slider>
<div class="flex align-items-center justify-content-between px-2">
<span>{{ filterModel.value ? filterModel.value[0] : 0 }}</span>
<span>{{ filterModel.value ? filterModel.value[1] : 100 }}</span>
</div>
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" bodyClass="text-center" style="min-width: 8rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500 ': data.verified, 'pi-times-circle text-red-500': !data.verified }"></i>
</template>
<template #filter="{ filterModel }">
<label for="verified-filter" class="font-bold"> Verified </label>
<TriStateCheckbox v-model="filterModel.value" inputId="verified-filter" />
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:filters="filters" :value="customers" paginator showGridlines :rows="10" dataKey="id"
filterDisplay="menu" :loading="loading" :globalFilterFields="['name', 'country.name', 'representative.name', 'balance', 'status']">
<template #header>
<div class="flex justify-content-between">
<Button type="button" icon="pi pi-filter-slash" label="Clear" class="p-button-outlined" @click="clearFilter()" />
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<template #loading> Loading customers data. Please wait. </template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
<template #filterclear="{ filterCallback }">
<Button type="button" icon="pi pi-times" @click="filterCallback()" class="p-button-secondary"></Button>
</template>
<template #filterapply="{ filterCallback }">
<Button type="button" icon="pi pi-check" @click="filterCallback()" class="p-button-success"></Button>
</template>
<template #filterfooter>
<div class="px-3 pt-0 pb-3 text-center">Customized Buttons</div>
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column header="Date" filterField="date" dataType="date" style="min-width: 10rem">
<template #body="{ data }">
{{ formatDate(data.date) }}
</template>
<template #filter="{ filterModel }">
<Calendar v-model="filterModel.value" dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />
</template>
</Column>
<Column header="Balance" filterField="balance" dataType="numeric" style="min-width: 10rem">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
<template #filter="{ filterModel }">
<InputNumber v-model="filterModel.value" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column header="Status" field="status" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="activity" header="Activity" :showFilterMatchModes="false" style="min-width: 12rem">
<template #body="{ data }">
<ProgressBar :value="data.activity" :showValue="false" style="height: 6px"></ProgressBar>
</template>
<template #filter="{ filterModel }">
<Slider v-model="filterModel.value" range class="m-3"></Slider>
<div class="flex align-items-center justify-content-between px-2">
<span>{{ filterModel.value ? filterModel.value[0] : 0 }}</span>
<span>{{ filterModel.value ? filterModel.value[1] : 100 }}</span>
</div>
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" bodyClass="text-center" style="min-width: 8rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500 ': data.verified, 'pi-times-circle text-red-500': !data.verified }"></i>
</template>
<template #filter="{ filterModel }">
<label for="verified-filter" class="font-bold"> Verified </label>
<TriStateCheckbox v-model="filterModel.value" inputId="verified-filter" />
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
export default {
data() {
return {
customers: null,
filters: null,
representatives: [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
],
statuses: ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal'],
loading: true
};
},
created() {
this.initFilters();
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = this.getCustomers(data);
this.loading = false;
});
},
methods: {
formatDate(value) {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
clearFilter() {
this.initFilters();
},
initFilters() {
this.filters = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
balance: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
activity: { value: [0, 100], matchMode: FilterMatchMode.BETWEEN },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
};
},
getCustomers(data) {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:filters="filters" :value="customers" paginator showGridlines :rows="10" dataKey="id"
filterDisplay="menu" :loading="loading" :globalFilterFields="['name', 'country.name', 'representative.name', 'balance', 'status']">
<template #header>
<div class="flex justify-content-between">
<Button type="button" icon="pi pi-filter-slash" label="Clear" class="p-button-outlined" @click="clearFilter()" />
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<template #loading> Loading customers data. Please wait. </template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
<template #filterclear="{ filterCallback }">
<Button type="button" icon="pi pi-times" @click="filterCallback()" class="p-button-secondary"></Button>
</template>
<template #filterapply="{ filterCallback }">
<Button type="button" icon="pi pi-check" @click="filterCallback()" class="p-button-success"></Button>
</template>
<template #filterfooter>
<div class="px-3 pt-0 pb-3 text-center">Customized Buttons</div>
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column header="Date" filterField="date" dataType="date" style="min-width: 10rem">
<template #body="{ data }">
{{ formatDate(data.date) }}
</template>
<template #filter="{ filterModel }">
<Calendar v-model="filterModel.value" dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />
</template>
</Column>
<Column header="Balance" filterField="balance" dataType="numeric" style="min-width: 10rem">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
<template #filter="{ filterModel }">
<InputNumber v-model="filterModel.value" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column header="Status" field="status" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="activity" header="Activity" :showFilterMatchModes="false" style="min-width: 12rem">
<template #body="{ data }">
<ProgressBar :value="data.activity" :showValue="false" style="height: 6px"></ProgressBar>
</template>
<template #filter="{ filterModel }">
<Slider v-model="filterModel.value" range class="m-3"></Slider>
<div class="flex align-items-center justify-content-between px-2">
<span>{{ filterModel.value ? filterModel.value[0] : 0 }}</span>
<span>{{ filterModel.value ? filterModel.value[1] : 100 }}</span>
</div>
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" bodyClass="text-center" style="min-width: 8rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500 ': data.verified, 'pi-times-circle text-red-500': !data.verified }"></i>
</template>
<template #filter="{ filterModel }">
<label for="verified-filter" class="font-bold"> Verified </label>
<TriStateCheckbox v-model="filterModel.value" inputId="verified-filter" />
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
const customers = ref();
const filters = ref();
const representatives = ref([
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
]);
const statuses = ref(['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal']);
const loading = ref(true);
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => {
customers.value = getCustomers(data);
loading.value = false;
});
});
const initFilters = () => {
filters.value = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
balance: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
activity: { value: [0, 100], matchMode: FilterMatchMode.BETWEEN },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
};
};
initFilters();
const formatDate = (value) => {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const clearFilter = () => {
initFilters();
};
const getCustomers = (data) => {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
};
const getSeverity = (status) => {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
};
<\/script>`
}
};
},
created() {
this.initFilters();
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = this.getCustomers(data);
this.loading = false;
});
},
methods: {
formatDate(value) {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
clearFilter() {
this.initFilters();
},
initFilters() {
this.filters = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
balance: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
activity: { value: [0, 100], matchMode: FilterMatchMode.BETWEEN },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
};
},
getCustomers(data) {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,554 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Data filtering is enabled by defining the <i>filters</i> property referring to a <i>DataTableFilterMeta</i> instance. Each column to filter also requires <i>filter</i> to be enabled. Built-in filter element is a input field and using
<i>filterElement</i>, it is possible to customize the filtering with your own UI. Filter elements are display within a separe row when <i>filterDisplay</i> is defined as <i>row</i>.
</p>
<p>The optional global filtering searches the data against a single value that is bound to the <i>global</i> key of the <i>filters</i> object. The fields to search against is defined with the <i>globalFilterFields</i>.</p>
</DocSectionText>
<div class="card">
<DataTable v-model:filters="filters" :value="customers" paginator :rows="10" dataKey="id" filterDisplay="row" :loading="loading" :globalFilterFields="['name', 'country.name', 'representative.name', 'status']">
<template #header>
<div class="flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<template #loading> Loading customers data. Please wait. </template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @input="filterCallback()" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${data.country.code}`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @input="filterCallback()" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMenu="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${data.representative.image}`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<MultiSelect v-model="filterModel.value" @change="filterCallback()" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter" style="min-width: 14rem" :maxSelectedLabels="1">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${slotProps.option.image}`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="status" header="Status" :showFilterMenu="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel, filterCallback }">
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="statuses" placeholder="Select One" class="p-column-filter" style="min-width: 12rem" :showClear="true">
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" style="min-width: 6rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500': data.verified, 'pi-times-circle text-red-400': !data.verified }"></i>
</template>
<template #filter="{ filterModel, filterCallback }">
<TriStateCheckbox v-model="filterModel.value" @change="filterCallback()" />
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode } from 'primevue/api';
export default {
data() {
return {
customers: null,
filters: {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
'country.name': { value: null, matchMode: FilterMatchMode.STARTS_WITH },
representative: { value: null, matchMode: FilterMatchMode.IN },
status: { value: null, matchMode: FilterMatchMode.EQUALS },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
},
representatives: [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
],
statuses: ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal'],
loading: true,
code: {
basic: `
<DataTable v-model:filters="filters" :value="customers" paginator :rows="10" dataKey="id" filterDisplay="row" :loading="loading"
:globalFilterFields="['name', 'country.name', 'representative.name', 'status']">
<template #header>
<div class="flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<template #loading> Loading customers data. Please wait. </template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @input="filterCallback()" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @input="filterCallback()" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMenu="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<MultiSelect v-model="filterModel.value" @change="filterCallback()" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter" style="min-width: 14rem" :maxSelectedLabels="1">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="status" header="Status" :showFilterMenu="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel, filterCallback }">
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="statuses" placeholder="Select One" class="p-column-filter" style="min-width: 12rem" :showClear="true">
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" style="min-width: 6rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500': data.verified, 'pi-times-circle text-red-400': !data.verified }"></i>
</template>
<template #filter="{ filterModel, filterCallback }">
<TriStateCheckbox v-model="filterModel.value" @change="filterCallback()" />
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:filters="filters" :value="customers" paginator :rows="10" dataKey="id" filterDisplay="row" :loading="loading"
:globalFilterFields="['name', 'country.name', 'representative.name', 'status']">
<template #header>
<div class="flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<template #loading> Loading customers data. Please wait. </template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @input="filterCallback()" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @input="filterCallback()" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMenu="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<MultiSelect v-model="filterModel.value" @change="filterCallback()" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter" style="min-width: 14rem" :maxSelectedLabels="1">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="status" header="Status" :showFilterMenu="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel, filterCallback }">
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="statuses" placeholder="Select One" class="p-column-filter" style="min-width: 12rem" :showClear="true">
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" style="min-width: 6rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500': data.verified, 'pi-times-circle text-red-400': !data.verified }"></i>
</template>
<template #filter="{ filterModel, filterCallback }">
<TriStateCheckbox v-model="filterModel.value" @change="filterCallback()" />
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { FilterMatchMode } from 'primevue/api';
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
filters: {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
'country.name': { value: null, matchMode: FilterMatchMode.STARTS_WITH },
representative: { value: null, matchMode: FilterMatchMode.IN },
status: { value: null, matchMode: FilterMatchMode.EQUALS },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
},
representatives: [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
],
statuses: ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal'],
loading: true
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = this.getCustomers(data);
this.loading = false;
});
},
methods: {
getCustomers(data) {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
},
formatDate(value) {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:filters="filters" :value="customers" paginator :rows="10" dataKey="id" filterDisplay="row" :loading="loading"
:globalFilterFields="['name', 'country.name', 'representative.name', 'status']">
<template #header>
<div class="flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<template #loading> Loading customers data. Please wait. </template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @input="filterCallback()" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<InputText v-model="filterModel.value" type="text" @input="filterCallback()" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMenu="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel, filterCallback }">
<MultiSelect v-model="filterModel.value" @change="filterCallback()" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter" style="min-width: 14rem" :maxSelectedLabels="1">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="status" header="Status" :showFilterMenu="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel, filterCallback }">
<Dropdown v-model="filterModel.value" @change="filterCallback()" :options="statuses" placeholder="Select One" class="p-column-filter" style="min-width: 12rem" :showClear="true">
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" style="min-width: 6rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500': data.verified, 'pi-times-circle text-red-400': !data.verified }"></i>
</template>
<template #filter="{ filterModel, filterCallback }">
<TriStateCheckbox v-model="filterModel.value" @change="filterCallback()" />
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { FilterMatchMode } from 'primevue/api';
import { CustomerService } from '@/service/CustomerService';
const customers = ref();
const filters = ref({
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { value: null, matchMode: FilterMatchMode.STARTS_WITH },
'country.name': { value: null, matchMode: FilterMatchMode.STARTS_WITH },
representative: { value: null, matchMode: FilterMatchMode.IN },
status: { value: null, matchMode: FilterMatchMode.EQUALS },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
});
const representatives = ref([
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
]);
const statuses = ref(['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal']);
const loading = ref(true);
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => {
customers.value = getCustomers(data);
loading.value = false;
});
});
const getCustomers = (data) => {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
};
const formatDate = (value) => {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const getSeverity = (status) => {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = this.getCustomers(data);
this.loading = false;
});
},
methods: {
getCustomers(data) {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
},
formatDate(value) {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,108 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Pagination is enabled by adding <i>paginator</i> property and defining <i>rows</i> per page.</p>
</DocSectionText>
<div class="card">
<DataTable :value="customers" paginator :rows="5" :rowsPerPageOptions="[5, 10, 20, 50]" tableStyle="min-width: 50rem">
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%"></Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
code: {
basic: `
<DataTable :value="customers" paginator :rows="5" :rowsPerPageOptions="[5, 10, 20, 50]" tableStyle="min-width: 50rem">
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%"></Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="customers" paginator :rows="5" :rowsPerPageOptions="[5, 10, 20, 50]" tableStyle="min-width: 50rem">
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%"></Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="customers" paginator :rows="5" :rowsPerPageOptions="[5, 10, 20, 50]" tableStyle="min-width: 50rem">
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%"></Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
const customers = ref();
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...
`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
}
};
</script>

View file

@ -0,0 +1,149 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Paginator UI is customized using the <i>paginatorTemplate</i> property. Each element can also be customized further with your own UI to replace the default one, refer to the <NuxtLink to="/paginator">Paginator</NuxtLink> component for
more information about the advanced customization options.
</p>
</DocSectionText>
<div class="card">
<DataTable
:value="customers"
paginator
:rows="5"
:rowsPerPageOptions="[5, 10, 20, 50]"
paginatorTemplate="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
currentPageReportTemplate="{first} to {last} of {totalRecords}"
tableStyle="min-width: 50rem"
>
<template #paginatorstart>
<Button type="button" icon="pi pi-refresh" class="p-button-text" />
</template>
<template #paginatorend>
<Button type="button" icon="pi pi-download" class="p-button-text" />
</template>
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%"></Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
code: {
basic: `
<DataTable :value="customers" paginator :rows="5" :rowsPerPageOptions="[5, 10, 20, 50]" tableStyle="min-width: 50rem"
paginatorTemplate="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
currentPageReportTemplate="{first} to {last} of {totalRecords}">
<template #paginatorstart>
<Button type="button" icon="pi pi-refresh" class="p-button-text" />
</template>
<template #paginatorend>
<Button type="button" icon="pi pi-download" class="p-button-text" />
</template>
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%"></Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="customers" paginator :rows="5" :rowsPerPageOptions="[5, 10, 20, 50]" tableStyle="min-width: 50rem"
paginatorTemplate="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
currentPageReportTemplate="{first} to {last} of {totalRecords}">
<template #paginatorstart>
<Button type="button" icon="pi pi-refresh" class="p-button-text" />
</template>
<template #paginatorend>
<Button type="button" icon="pi pi-download" class="p-button-text" />
</template>
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%"></Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="customers" paginator :rows="5" :rowsPerPageOptions="[5, 10, 20, 50]" tableStyle="min-width: 50rem"
paginatorTemplate="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
currentPageReportTemplate="{first} to {last} of {totalRecords}">
<template #paginatorstart>
<Button type="button" icon="pi pi-refresh" class="p-button-text" />
</template>
<template #paginatorend>
<Button type="button" icon="pi pi-download" class="p-button-text" />
</template>
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%"></Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
const customers = ref();
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...
`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
}
};
</script>

View file

@ -0,0 +1,337 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
When <i>expandableRowGroups</i> is present in subheader based row grouping, groups can be expanded and collapsed. State of the expansions are controlled using the <i>expandedRows</i> property and <i>rowgroup-expand</i> and
<i>rowgroup-collapse</i> events.
</p>
</DocSectionText>
<div class="card">
<DataTable
v-model:expandedRowGroups="expandedRowGroups"
:value="customers"
expandableRowGroups
rowGroupMode="subheader"
groupRowsBy="representative.name"
sortMode="single"
sortField="representative.name"
:sortOrder="1"
@rowgroup-expand="onRowGroupExpand"
@rowgroup-collapse="onRowGroupCollapse"
tableStyle="min-width: 50rem"
>
<template #groupheader="slotProps">
<img :alt="slotProps.data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${slotProps.data.representative.image}`" width="32" style="vertical-align: middle" class="ml-2" />
<span class="vertical-align-middle ml-2 font-bold line-height-3">{{ slotProps.data.representative.name }}</span>
</template>
<Column field="representative.name" header="Representative"></Column>
<Column field="name" header="Name" style="width: 20%"></Column>
<Column field="country" header="Country" style="width: 20%">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${slotProps.data.country.code}`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="width: 20%"></Column>
<Column field="status" header="Status" style="width: 20%">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="width: 20%"></Column>
<template #groupfooter="slotProps">
<td colspan="5">
<div class="flex justify-content-end font-bold w-full">Total Customers: {{ calculateCustomerTotal(slotProps.data.representative.name) }}</div>
</td>
</template>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
expandedRowGroups: null,
code: {
basic: `
<DataTable v-model:expandedRowGroups="expandedRowGroups" :value="customers" tableStyle="min-width: 50rem"
expandableRowGroups rowGroupMode="subheader" groupRowsBy="representative.name" @rowgroup-expand="onRowGroupExpand" @rowgroup-collapse="onRowGroupCollapse"
sortMode="single" sortField="representative.name" :sortOrder="1">
<template #groupheader="slotProps">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" class="ml-2" />
<span class="vertical-align-middle ml-2 font-bold line-height-3">{{ slotProps.data.representative.name }}</span>
</template>
<Column field="representative.name" header="Representative"></Column>
<Column field="name" header="Name" style="width: 20%"></Column>
<Column field="country" header="Country" style="width: 20%">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="width: 20%"></Column>
<Column field="status" header="Status" style="width: 20%">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="width: 20%"></Column>
<template #groupfooter="slotProps">
<td colspan="5">
<div class="flex justify-content-end font-bold w-full">Total Customers: {{ calculateCustomerTotal(slotProps.data.representative.name) }}</div>
</td>
</template>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:expandedRowGroups="expandedRowGroups" :value="customers" tableStyle="min-width: 50rem"
expandableRowGroups rowGroupMode="subheader" groupRowsBy="representative.name" @rowgroup-expand="onRowGroupExpand" @rowgroup-collapse="onRowGroupCollapse"
sortMode="single" sortField="representative.name" :sortOrder="1">
<template #groupheader="slotProps">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" class="ml-2" />
<span class="vertical-align-middle ml-2 font-bold line-height-3">{{ slotProps.data.representative.name }}</span>
</template>
<Column field="representative.name" header="Representative"></Column>
<Column field="name" header="Name" style="width: 20%"></Column>
<Column field="country" header="Country" style="width: 20%">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="width: 20%"></Column>
<Column field="status" header="Status" style="width: 20%">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="width: 20%"></Column>
<template #groupfooter="slotProps">
<td colspan="5">
<div class="flex justify-content-end font-bold w-full">Total Customers: {{ calculateCustomerTotal(slotProps.data.representative.name) }}</div>
</td>
</template>
</DataTable>
<Toast />
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
expandedRowGroups: null
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
},
methods: {
onRowGroupExpand(event) {
this.$toast.add({ severity: 'info', summary: 'Row Group Expanded', detail: 'Value: ' + event.data, life: 3000 });
},
onRowGroupCollapse(event) {
this.$toast.add({ severity: 'success', summary: 'Row Group Collapsed', detail: 'Value: ' + event.data, life: 3000 });
},
calculateCustomerTotal(name) {
let total = 0;
if (this.customers) {
for (let customer of this.customers) {
if (customer.representative.name === name) {
total++;
}
}
}
return total;
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:expandedRowGroups="expandedRowGroups" :value="customers" tableStyle="min-width: 50rem"
expandableRowGroups rowGroupMode="subheader" groupRowsBy="representative.name" @rowgroup-expand="onRowGroupExpand" @rowgroup-collapse="onRowGroupCollapse"
sortMode="single" sortField="representative.name" :sortOrder="1">
<template #groupheader="slotProps">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" class="ml-2" />
<span class="vertical-align-middle ml-2 font-bold line-height-3">{{ slotProps.data.representative.name }}</span>
</template>
<Column field="representative.name" header="Representative"></Column>
<Column field="name" header="Name" style="width: 20%"></Column>
<Column field="country" header="Country" style="width: 20%">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="width: 20%"></Column>
<Column field="status" header="Status" style="width: 20%">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="width: 20%"></Column>
<template #groupfooter="slotProps">
<td colspan="5">
<div class="flex justify-content-end font-bold w-full">Total Customers: {{ calculateCustomerTotal(slotProps.data.representative.name) }}</div>
</td>
</template>
</DataTable>
<Toast />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useToast } from 'primevue/usetoast';
import { CustomerService } from '@/service/CustomerService';
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
const customers = ref();
const expandedRowGroups = ref();
const toast = useToast();
const onRowGroupExpand = (event) => {
toast.add({ severity: 'info', summary: 'Row Group Expanded', detail: 'Value: ' + event.data, life: 3000 });
};
const onRowGroupCollapse = (event) => {
toast.add({ severity: 'success', summary: 'Row Group Collapsed', detail: 'Value: ' + event.data, life: 3000 });
};
const calculateCustomerTotal = (name) => {
let total = 0;
if (customers.value) {
for (let customer of customers.value) {
if (customer.representative.name === name) {
total++;
}
}
}
return total;
};
const getSeverity = (status) => {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
};
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
},
methods: {
onRowGroupExpand(event) {
this.$toast.add({ severity: 'info', summary: 'Row Group Expanded', detail: 'Value: ' + event.data, life: 3000 });
},
onRowGroupCollapse(event) {
this.$toast.add({ severity: 'success', summary: 'Row Group Collapsed', detail: 'Value: ' + event.data, life: 3000 });
},
calculateCustomerTotal(name) {
let total = 0;
if (this.customers) {
for (let customer of this.customers) {
if (customer.representative.name === name) {
total++;
}
}
}
return total;
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,285 @@
<template>
<DocSectionText v-bind="$attrs">
<p>When <i>rowGroupMode</i> is configured to be <i>rowspan</i>, the grouping column spans multiple rows.</p>
</DocSectionText>
<div class="card">
<DataTable :value="customers" rowGroupMode="rowspan" groupRowsBy="representative.name" sortMode="single" sortField="representative.name" :sortOrder="1" tableStyle="min-width: 50rem">
<Column header="#" headerStyle="width:3rem">
<template #body="slotProps">
{{ slotProps.index + 1 }}
</template>
</Column>
<Column field="representative.name" header="Representative" style="min-width: 200px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${slotProps.data.representative.image}`" width="32" style="vertical-align: middle" />
<span>{{ slotProps.data.representative.name }}</span>
</div>
</template>
</Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country" header="Country" style="min-width: 150px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${slotProps.data.country.code}`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 100px">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="min-width: 100px"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
expandedRowGroups: null,
code: {
basic: `
<DataTable :value="customers" rowGroupMode="rowspan" groupRowsBy="representative.name" sortMode="single" sortField="representative.name" :sortOrder="1" tableStyle="min-width: 50rem">
<Column header="#" headerStyle="width:3rem">
<template #body="slotProps">
{{ slotProps.index + 1 }}
</template>
</Column>
<Column field="representative.name" header="Representative" style="min-width: 200px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" />
<span>{{ slotProps.data.representative.name }}</span>
</div>
</template>
</Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country" header="Country" style="min-width: 150px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 100px">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="min-width: 100px"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="customers" rowGroupMode="rowspan" groupRowsBy="representative.name" sortMode="single" sortField="representative.name" :sortOrder="1" tableStyle="min-width: 50rem">
<Column header="#" headerStyle="width:3rem">
<template #body="slotProps">
{{ slotProps.index + 1 }}
</template>
</Column>
<Column field="representative.name" header="Representative" style="min-width: 200px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" />
<span>{{ slotProps.data.representative.name }}</span>
</div>
</template>
</Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country" header="Country" style="min-width: 150px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 100px">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="min-width: 100px"></Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
},
methods: {
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="customers" rowGroupMode="rowspan" groupRowsBy="representative.name" sortMode="single" sortField="representative.name" :sortOrder="1" tableStyle="min-width: 50rem">
<Column header="#" headerStyle="width:3rem">
<template #body="slotProps">
{{ slotProps.index + 1 }}
</template>
</Column>
<Column field="representative.name" header="Representative" style="min-width: 200px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" />
<span>{{ slotProps.data.representative.name }}</span>
</div>
</template>
</Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country" header="Country" style="min-width: 150px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 100px">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="min-width: 100px"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useToast } from 'primevue/usetoast';
import { CustomerService } from '@/service/CustomerService';
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
const customers = ref();
const getSeverity = (status) => {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
};
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
},
methods: {
onRowGroupExpand(event) {
this.$toast.add({ severity: 'info', summary: 'Row Group Expanded', detail: 'Value: ' + event.data, life: 3000 });
},
onRowGroupCollapse(event) {
this.$toast.add({ severity: 'success', summary: 'Row Group Collapsed', detail: 'Value: ' + event.data, life: 3000 });
},
calculateCustomerTotal(name) {
let total = 0;
if (this.customers) {
for (let customer of this.customers) {
if (customer.representative.name === name) {
total++;
}
}
}
return total;
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,305 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Rows are grouped with the <i>groupRowsBy</i> property. When <i>rowGroupMode</i> is set as <i>subheader</i>, a header and footer can be displayed for each group. The content of a group header is provided with <i>groupheader</i> and footer
with <i>groupfooter</i> slots.
</p>
</DocSectionText>
<div class="card">
<DataTable :value="customers" rowGroupMode="subheader" groupRowsBy="representative.name" sortMode="single" sortField="representative.name" :sortOrder="1" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<template #groupheader="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${slotProps.data.representative.image}`" width="32" style="vertical-align: middle" />
<span>{{ slotProps.data.representative.name }}</span>
</div>
</template>
<Column field="representative.name" header="Representative"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country" header="Country" style="min-width: 200px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${slotProps.data.country.code}`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<template #groupfooter="slotProps">
<td colspan="5">
<div class="flex justify-content-end font-bold w-full">Total Customers: {{ calculateCustomerTotal(slotProps.data.representative.name) }}</div>
</td>
</template>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
code: {
basic: `
<DataTable :value="customers" rowGroupMode="subheader" groupRowsBy="representative.name" sortMode="single"
sortField="representative.name" :sortOrder="1" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="representative.name" header="Representative"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country" header="Country" style="min-width: 200px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<template #groupheader="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" />
<span>{{ slotProps.data.representative.name }}</span>
</div>
</template>
<template #groupfooter="slotProps">
<td colspan="5">
<div class="flex justify-content-end font-bold w-full">Total Customers: {{ calculateCustomerTotal(slotProps.data.representative.name) }}</div>
</td>
</template>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="customers" rowGroupMode="subheader" groupRowsBy="representative.name" sortMode="single"
sortField="representative.name" :sortOrder="1" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="representative.name" header="Representative"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country" header="Country" style="min-width: 200px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<template #groupheader="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" />
<span>{{ slotProps.data.representative.name }}</span>
</div>
</template>
<template #groupfooter="slotProps">
<td colspan="5">
<div class="flex justify-content-end font-bold w-full">Total Customers: {{ calculateCustomerTotal(slotProps.data.representative.name) }}</div>
</td>
</template>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
},
methods: {
calculateCustomerTotal(name) {
let total = 0;
if (this.customers) {
for (let customer of this.customers) {
if (customer.representative.name === name) {
total++;
}
}
}
return total;
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="customers" rowGroupMode="subheader" groupRowsBy="representative.name" sortMode="single"
sortField="representative.name" :sortOrder="1" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="representative.name" header="Representative"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country" header="Country" style="min-width: 200px">
<template #body="slotProps">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${slotProps.data.country.code}\`" style="width: 24px" />
<span>{{ slotProps.data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px">
<template #body="slotProps">
<Tag :value="slotProps.data.status" :severity="getSeverity(slotProps.data.status)" />
</template>
</Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<template #groupheader="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.data.representative.image}\`" width="32" style="vertical-align: middle" />
<span>{{ slotProps.data.representative.name }}</span>
</div>
</template>
<template #groupfooter="slotProps">
<td colspan="5">
<div class="flex justify-content-end font-bold w-full">Total Customers: {{ calculateCustomerTotal(slotProps.data.representative.name) }}</div>
</td>
</template>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
const customers = ref();
const calculateCustomerTotal = (name) => {
let total = 0;
if (customers.value) {
for (let customer of customers.value) {
if (customer.representative.name === name) {
total++;
}
}
}
return total;
};
const getSeverity = (status) => {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
};
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => (this.customers = data));
},
methods: {
calculateCustomerTotal(name) {
let total = 0;
if (this.customers) {
for (let customer of this.customers) {
if (customer.representative.name === name) {
total++;
}
}
}
return total;
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,116 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
More than one row is selectable by setting <i>selectionMode</i> to <i>multiple</i>. By default in multiple selection mode, metaKey press (e.g. <i></i>) is necessary to add to existing selections however this can be configured with
disabling the <i>metaKeySelection</i> property. Note that in touch enabled devices, DataTable always ignores metaKey.
</p>
<p>The header checkbox toggles the selection state of the whole dataset by default, when paginator is enabled you may add <i>selectAll</i> property and <i>select-all-change</i> event to only control the selection of visible rows.</p>
</DocSectionText>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true,
code: {
basic: `
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const selectedProduct = ref();
const metaKey = ref(true);
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,125 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
More than one row is selectable by setting <i>selectionMode</i> to <i>multiple</i>. By default in multiple selection mode, metaKey press (e.g. <i></i>) is necessary to add to existing selections however this can be configured with
disabling the <i>metaKeySelection</i> property. Note that in touch enabled devices, DataTable always ignores metaKey.
</p>
</DocSectionText>
<div class="card">
<div class="flex justify-content-center align-items-center mb-4 gap-2">
<InputSwitch v-model="metaKey" inputId="input-metakey" />
<label for="input-metakey">MetaKey</label>
</div>
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="multiple" :metaKeySelection="metaKey" dataKey="id" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true,
code: {
basic: `
<InputSwitch v-model="metaKey" inputId="input-metakey" />
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="multiple" :metaKeySelection="metaKey" dataKey="id" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<div class="flex justify-content-center align-items-center mb-4 gap-2">
<InputSwitch v-model="metaKey" inputId="input-metakey" />
<label for="input-metakey">MetaKey</label>
</div>
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="multiple" :metaKeySelection="metaKey" dataKey="id" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<div class="flex justify-content-center align-items-center mb-4 gap-2">
<InputSwitch v-model="metaKey" inputId="input-metakey" />
<label for="input-metakey">MetaKey</label>
</div>
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="multiple" :metaKeySelection="metaKey" dataKey="id" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const selectedProduct = ref();
const metaKey = ref(true);
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,115 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Specifying <i>selectionMode</i> as <i>single</i> on a Column, displays a radio button inside that column for selection. By default, row clicks also trigger selection, set <i>selectionMode</i> of DataTable to <i>radiobutton</i> to only
trigger selection using the radio buttons.
</p>
</DocSectionText>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="single" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true,
code: {
basic: `
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="single" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="single" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="single" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const selectedProduct = ref();
const metaKey = ref(true);
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,136 @@
<template>
<DocSectionText v-bind="$attrs">
<p>DataTable provides <i>row-select</i> and <i>row-unselect</i> events to listen selection events.</p>
</DocSectionText>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" dataKey="id" :metaKeySelection="false" @rowSelect="onRowSelect" @rowUnselect="onRowUnselect" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true,
code: {
basic: `
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" dataKey="id" :metaKeySelection="false"
@rowSelect="onRowSelect" @rowUnselect="onRowUnselect" tableStyle="min-width: 50rem">
<Column selectionMode="single" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" dataKey="id" :metaKeySelection="false"
@rowSelect="onRowSelect" @rowUnselect="onRowUnselect" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
<Toast/>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onRowSelect(event) {
this.$toast.add({ severity: 'info', summary: 'Product Selected', detail: 'Name: ' + event.data.name, life: 3000 });
},
onRowUnselect(event) {
this.$toast.add({ severity: 'warn', summary: 'Product Unselected', detail: 'Name: ' + event.data.name, life: 3000 });
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" dataKey="id" :metaKeySelection="false"
@rowSelect="onRowSelect" @rowUnselect="onRowUnselect" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
<Toast/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useToast } from 'primevue/usetoast';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const selectedProduct = ref();
const toast = useToast();
const onRowSelect = (event) => {
toast.add({ severity: 'info', summary: 'Product Selected', detail: 'Name: ' + event.data.name, life: 3000 });
};
const onRowUnselect = (event) => {
toast.add({ severity: 'warn', summary: 'Product Unselected', detail: 'Name: ' + event.data.name, life: 3000 });
}
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
onRowSelect(event) {
this.$toast.add({ severity: 'info', summary: 'Product Selected', detail: 'Name: ' + event.data.name, life: 3000 });
},
onRowUnselect(event) {
this.$toast.add({ severity: 'warn', summary: 'Product Unselected', detail: 'Name: ' + event.data.name, life: 3000 });
}
}
};
</script>

View file

@ -0,0 +1,129 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Single row selection is enabled by defining <i>selectionMode</i> as <i>single</i> along with a value binding using <i>selection</i> property. When available, it is suggested to provide a unique identifier of a row with <i>dataKey</i> to
optimize performance.
</p>
<p>
By default, metaKey press (e.g. <i></i>) is necessary to unselect a row however this can be configured with disabling the <i>metaKeySelection</i> property. In touch enabled devices this option has no effect and behavior is same as
setting it to false.
</p>
</DocSectionText>
<div class="card">
<div class="flex justify-content-center align-items-center mb-4 gap-2">
<InputSwitch v-model="metaKey" inputId="input-metakey" />
<label for="input-metakey">MetaKey</label>
</div>
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :metaKeySelection="metaKey" dataKey="id" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true,
code: {
basic: `
<InputSwitch v-model="metaKey" inputId="input-metakey" />
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :metaKeySelection="metaKey" dataKey="id" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<div class="flex justify-content-center align-items-center mb-4 gap-2">
<InputSwitch v-model="metaKey" inputId="input-metakey" />
<label for="input-metakey">MetaKey</label>
</div>
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :metaKeySelection="metaKey" dataKey="id" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
selectedProduct: null,
metaKey: true
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<div class="flex justify-content-center align-items-center mb-4 gap-2">
<InputSwitch v-model="metaKey" inputId="input-metakey" />
<label for="input-metakey">MetaKey</label>
</div>
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :metaKeySelection="metaKey" dataKey="id" tableStyle="min-width: 50rem">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const selectedProduct = ref();
const metaKey = ref(true);
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,667 @@
<template>
<DocSectionText v-bind="$attrs">
<p>DataTable with selection, pagination, filtering, sorting and templating.</p>
</DocSectionText>
<div class="card">
<DataTable v-model:filters="filters" v-model:selection="selectedCustomers" :value="customers" paginator :rows="10" dataKey="id" filterDisplay="menu" :globalFilterFields="['name', 'country.name', 'representative.name', 'balance', 'status']">
<template #header>
<div class="flex justify-content-between">
<Button type="button" icon="pi pi-filter-slash" label="Clear" class="p-button-outlined" @click="clearFilter()" />
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="name" header="Name" sortable style="min-width: 14rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" sortable sortField="country.name" filterField="country.name" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${data.country.code}`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Agent" sortable sortField="representative.name" filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${data.representative.image}`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${slotProps.option.image}`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="date" header="Date" sortable filterField="date" dataType="date" style="min-width: 10rem">
<template #body="{ data }">
{{ formatDate(data.date) }}
</template>
<template #filter="{ filterModel }">
<Calendar v-model="filterModel.value" dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />
</template>
</Column>
<Column field="balance" header="Balance" sortable filterField="balance" dataType="numeric" style="min-width: 10rem">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
<template #filter="{ filterModel }">
<InputNumber v-model="filterModel.value" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column header="Status" sortable field="status" :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="activity" header="Activity" sortable :showFilterMatchModes="false" style="min-width: 12rem">
<template #body="{ data }">
<ProgressBar :value="data.activity" :showValue="false" style="height: 6px"></ProgressBar>
</template>
<template #filter="{ filterModel }">
<Slider v-model="filterModel.value" range class="m-3"></Slider>
<div class="flex align-items-center justify-content-between px-2">
<span>{{ filterModel.value ? filterModel.value[0] : 0 }}</span>
<span>{{ filterModel.value ? filterModel.value[1] : 100 }}</span>
</div>
</template>
</Column>
<Column headerStyle="width: 5rem; text-align: center" bodyStyle="text-align: center; overflow: visible">
<template #body>
<Button type="button" icon="pi pi-cog" class="p-button-rounded" />
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
export default {
data() {
return {
customers: null,
selectedCustomers: null,
filters: null,
representatives: [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
],
statuses: ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal'],
code: {
basic: `
<DataTable v-model:filters="filters" v-model:selection="selectedCustomers" :value="customers" paginator :rows="10" dataKey="id" filterDisplay="menu"
:globalFilterFields="['name', 'country.name', 'representative.name', 'balance', 'status']">
<template #header>
<div class="flex justify-content-between">
<Button type="button" icon="pi pi-filter-slash" label="Clear" class="p-button-outlined" @click="clearFilter()" />
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="name" header="Name" sortable style="min-width: 14rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" sortable sortField="country.name" filterField="country.name" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Agent" sortable sortField="representative.name" filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="date" header="Date" sortable filterField="date" dataType="date" style="min-width: 10rem">
<template #body="{ data }">
{{ formatDate(data.date) }}
</template>
<template #filter="{ filterModel }">
<Calendar v-model="filterModel.value" dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />
</template>
</Column>
<Column field="balance" header="Balance" sortable filterField="balance" dataType="numeric" style="min-width: 10rem">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
<template #filter="{ filterModel }">
<InputNumber v-model="filterModel.value" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column header="Status" field="status" sortable :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="activity" header="Activity" sortable :showFilterMatchModes="false" style="min-width: 12rem">
<template #body="{ data }">
<ProgressBar :value="data.activity" :showValue="false" style="height: 6px"></ProgressBar>
</template>
<template #filter="{ filterModel }">
<Slider v-model="filterModel.value" range class="m-3"></Slider>
<div class="flex align-items-center justify-content-between px-2">
<span>{{ filterModel.value ? filterModel.value[0] : 0 }}</span>
<span>{{ filterModel.value ? filterModel.value[1] : 100 }}</span>
</div>
</template>
</Column>
<Column headerStyle="width: 5rem; text-align: center" bodyStyle="text-align: center; overflow: visible">
<template #body>
<Button type="button" icon="pi pi-cog" class="p-button-rounded" />
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable v-model:filters="filters" v-model:selection="selectedCustomers" :value="customers" paginator :rows="10" dataKey="id" filterDisplay="menu"
:globalFilterFields="['name', 'country.name', 'representative.name', 'balance', 'status']">
<template #header>
<div class="flex justify-content-between">
<Button type="button" icon="pi pi-filter-slash" label="Clear" class="p-button-outlined" @click="clearFilter()" />
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="name" header="Name" sortable style="min-width: 14rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" sortable sortField="country.name" filterField="country.name" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Agent" sortable sortField="representative.name" filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="date" header="Date" sortable filterField="date" dataType="date" style="min-width: 10rem">
<template #body="{ data }">
{{ formatDate(data.date) }}
</template>
<template #filter="{ filterModel }">
<Calendar v-model="filterModel.value" dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />
</template>
</Column>
<Column field="balance" header="Balance" sortable filterField="balance" dataType="numeric" style="min-width: 10rem">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
<template #filter="{ filterModel }">
<InputNumber v-model="filterModel.value" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column header="Status" field="status" sortable :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="activity" header="Activity" sortable :showFilterMatchModes="false" style="min-width: 12rem">
<template #body="{ data }">
<ProgressBar :value="data.activity" :showValue="false" style="height: 6px"></ProgressBar>
</template>
<template #filter="{ filterModel }">
<Slider v-model="filterModel.value" range class="m-3"></Slider>
<div class="flex align-items-center justify-content-between px-2">
<span>{{ filterModel.value ? filterModel.value[0] : 0 }}</span>
<span>{{ filterModel.value ? filterModel.value[1] : 100 }}</span>
</div>
</template>
</Column>
<Column headerStyle="width: 5rem; text-align: center" bodyStyle="text-align: center; overflow: visible">
<template #body>
<Button type="button" icon="pi pi-cog" class="p-button-rounded" />
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
export default {
data() {
return {
customers: null,
selectedCustomers: null,
filters: null,
representatives: [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
],
statuses: ['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal']
};
},
created() {
this.initFilters();
},
mounted() {
CustomerService.getCustomersLarge().then((data) => {
this.customers = this.getCustomers(data);
});
},
methods: {
formatDate(value) {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
clearFilter() {
this.initFilters();
},
initFilters() {
this.filters = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
balance: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
activity: { value: [0, 100], matchMode: FilterMatchMode.BETWEEN },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
};
},
getCustomers(data) {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable v-model:filters="filters" v-model:selection="selectedCustomers" :value="customers" paginator :rows="10" dataKey="id" filterDisplay="menu"
:globalFilterFields="['name', 'country.name', 'representative.name', 'balance', 'status']">
<template #header>
<div class="flex justify-content-between">
<Button type="button" icon="pi pi-filter-slash" label="Clear" class="p-button-outlined" @click="clearFilter()" />
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
</span>
</div>
</template>
<template #empty> No customers found. </template>
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="name" header="Name" sortable style="min-width: 14rem">
<template #body="{ data }">
{{ data.name }}
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by name" />
</template>
</Column>
<Column header="Country" sortable sortField="country.name" filterField="country.name" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="\`flag flag-\${data.country.code}\`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<InputText v-model="filterModel.value" type="text" class="p-column-filter" placeholder="Search by country" />
</template>
</Column>
<Column header="Agent" sortable sortField="representative.name" filterField="representative" :showFilterMatchModes="false" :filterMenuStyle="{ width: '14rem' }" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex align-items-center gap-2">
<img :alt="data.representative.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${data.representative.image}\`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
<template #filter="{ filterModel }">
<MultiSelect v-model="filterModel.value" :options="representatives" optionLabel="name" placeholder="Any" class="p-column-filter">
<template #option="slotProps">
<div class="flex align-items-center gap-2">
<img :alt="slotProps.option.name" :src="\`https://primefaces.org/cdn/primevue/images/avatar/\${slotProps.option.image}\`" style="width: 32px" />
<span>{{ slotProps.option.name }}</span>
</div>
</template>
</MultiSelect>
</template>
</Column>
<Column field="date" header="Date" sortable filterField="date" dataType="date" style="min-width: 10rem">
<template #body="{ data }">
{{ formatDate(data.date) }}
</template>
<template #filter="{ filterModel }">
<Calendar v-model="filterModel.value" dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999" />
</template>
</Column>
<Column field="balance" header="Balance" sortable filterField="balance" dataType="numeric" style="min-width: 10rem">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
<template #filter="{ filterModel }">
<InputNumber v-model="filterModel.value" mode="currency" currency="USD" locale="en-US" />
</template>
</Column>
<Column header="Status" field="status" sortable :filterMenuStyle="{ width: '14rem' }" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
<template #filter="{ filterModel }">
<Dropdown v-model="filterModel.value" :options="statuses" placeholder="Select One" class="p-column-filter" showClear>
<template #option="slotProps">
<Tag :value="slotProps.option" :severity="getSeverity(slotProps.option)" />
</template>
</Dropdown>
</template>
</Column>
<Column field="activity" header="Activity" sortable :showFilterMatchModes="false" style="min-width: 12rem">
<template #body="{ data }">
<ProgressBar :value="data.activity" :showValue="false" style="height: 6px"></ProgressBar>
</template>
<template #filter="{ filterModel }">
<Slider v-model="filterModel.value" range class="m-3"></Slider>
<div class="flex align-items-center justify-content-between px-2">
<span>{{ filterModel.value ? filterModel.value[0] : 0 }}</span>
<span>{{ filterModel.value ? filterModel.value[1] : 100 }}</span>
</div>
</template>
</Column>
<Column headerStyle="width: 5rem; text-align: center" bodyStyle="text-align: center; overflow: visible">
<template #body>
<Button type="button" icon="pi pi-cog" class="p-button-rounded" />
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
import { FilterMatchMode, FilterOperator } from 'primevue/api';
const customers = ref();
const selectedCustomers = ref();
const filters = ref();
const representatives = ref([
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' }
]);
const statuses = ref(['unqualified', 'qualified', 'new', 'negotiation', 'renewal', 'proposal']);
onMounted(() => {
CustomerService.getCustomersLarge().then((data) => {
customers.value = getCustomers(data);
});
});
const initFilters = () => {
filters.value = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
balance: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
activity: { value: [0, 100], matchMode: FilterMatchMode.BETWEEN },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
};
};
initFilters();
const formatDate = (value) => {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const clearFilter = () => {
initFilters();
};
const getCustomers = (data) => {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
};
const getSeverity = (status) => {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
};
<\/script>`
}
};
},
created() {
this.initFilters();
},
mounted() {
CustomerService.getCustomersLarge().then((data) => {
this.customers = this.getCustomers(data);
});
},
methods: {
formatDate(value) {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
},
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
clearFilter() {
this.initFilters();
},
initFilters() {
this.filters = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
'country.name': { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }] },
representative: { value: null, matchMode: FilterMatchMode.IN },
date: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }] },
balance: { operator: FilterOperator.AND, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
status: { operator: FilterOperator.OR, constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }] },
activity: { value: [0, 100], matchMode: FilterMatchMode.BETWEEN },
verified: { value: null, matchMode: FilterMatchMode.EQUALS }
};
},
getCustomers(data) {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
},
getSeverity(status) {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warning';
case 'renewal':
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,899 @@
<template>
<DocSectionText v-bind="$attrs">
<p>CRUD implementation example with a Dialog.</p>
</DocSectionText>
<div class="card">
<Toolbar class="mb-4">
<template #start>
<Button label="New" icon="pi pi-plus" class="p-button-success mr-2" @click="openNew" />
<Button label="Delete" icon="pi pi-trash" class="p-button-danger" @click="confirmDeleteSelected" :disabled="!selectedProducts || !selectedProducts.length" />
</template>
<template #end>
<FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import" chooseLabel="Import" class="mr-2 inline-block" />
<Button label="Export" icon="pi pi-upload" class="p-button-help" @click="exportCSV($event)" />
</template>
</Toolbar>
<DataTable
ref="dt"
v-model:selection="selectedProducts"
:value="products"
dataKey="id"
:paginator="true"
:rows="10"
:filters="filters"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
:rowsPerPageOptions="[5, 10, 25]"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products"
>
<template #header>
<div class="flex flex-wrap gap-2 align-items-center justify-content-between">
<h4 class="m-0">Manage Products</h4>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Search..." />
</span>
</div>
</template>
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
<Column field="code" header="Code" sortable style="min-width: 12rem"></Column>
<Column field="name" header="Name" sortable style="min-width: 16rem"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="shadow-2 border-round" style="width: 64px" />
</template>
</Column>
<Column field="price" header="Price" sortable style="min-width: 8rem">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category" sortable style="min-width: 10rem"></Column>
<Column field="rating" header="Reviews" sortable style="min-width: 12rem">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" />
</template>
</Column>
<Column field="inventoryStatus" header="Status" sortable style="min-width: 12rem">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" />
</template>
</Column>
<Column :exportable="false" style="min-width: 12rem">
<template #body="slotProps">
<Button icon="pi pi-pencil" class="p-button-icon-only p-button-outlined p-button-rounded mr-2" @click="editProduct(slotProps.data)" />
<Button icon="pi pi-trash" class="p-button-icon-only p-button-outlined p-button-rounded p-button-danger" @click="confirmDeleteProduct(slotProps.data)" />
</template>
</Column>
</DataTable>
</div>
<Dialog v-model:visible="productDialog" :style="{ width: '450px' }" header="Product Details" :modal="true" class="p-fluid">
<img v-if="product.image" :src="`https://primefaces.org/cdn/primevue/images/product/${product.image}`" :alt="product.image" class="block m-auto pb-3" />
<div class="field">
<label for="name">Name</label>
<InputText id="name" v-model.trim="product.name" required="true" autofocus :class="{ 'p-invalid': submitted && !product.name }" />
<small v-if="submitted && !product.name" class="p-error">Name is required.</small>
</div>
<div class="field">
<label for="description">Description</label>
<Textarea id="description" v-model="product.description" required="true" rows="3" cols="20" />
</div>
<div class="field">
<label for="inventoryStatus" class="mb-3">Inventory Status</label>
<Dropdown id="inventoryStatus" v-model="product.inventoryStatus" :options="statuses" optionLabel="label" placeholder="Select a Status">
<template #value="slotProps">
<div v-if="slotProps.value && slotProps.value.value">
<Tag :value="slotProps.value.value" :severity="getStatusLabel(slotProps.value.label)" />
</div>
<div v-else-if="slotProps.value && !slotProps.value.value">
<Tag :value="slotProps.value" :severity="getStatusLabel(slotProps.value)" />
</div>
<span v-else>
{{ slotProps.placeholder }}
</span>
</template>
</Dropdown>
</div>
<div class="field">
<label class="mb-3">Category</label>
<div class="formgrid grid">
<div class="field-radiobutton col-6">
<RadioButton id="category1" v-model="product.category" name="category" value="Accessories" />
<label for="category1">Accessories</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category2" v-model="product.category" name="category" value="Clothing" />
<label for="category2">Clothing</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category3" v-model="product.category" name="category" value="Electronics" />
<label for="category3">Electronics</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category4" v-model="product.category" name="category" value="Fitness" />
<label for="category4">Fitness</label>
</div>
</div>
</div>
<div class="formgrid grid">
<div class="field col">
<label for="price">Price</label>
<InputNumber id="price" v-model="product.price" mode="currency" currency="USD" locale="en-US" />
</div>
<div class="field col">
<label for="quantity">Quantity</label>
<InputNumber id="quantity" v-model="product.quantity" integeronly />
</div>
</div>
<template #footer>
<Button label="Cancel" icon="pi pi-times" class="p-button-text" @click="hideDialog" />
<Button label="Save" icon="pi pi-check" class="p-button-text" @click="saveProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductDialog" :style="{ width: '450px' }" header="Confirm" :modal="true">
<div class="confirmation-content">
<i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" />
<span v-if="product"
>Are you sure you want to delete <b>{{ product.name }}</b
>?</span
>
</div>
<template #footer>
<Button label="No" icon="pi pi-times" class="p-button-text" @click="deleteProductDialog = false" />
<Button label="Yes" icon="pi pi-check" class="p-button-text" @click="deleteProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductsDialog" :style="{ width: '450px' }" header="Confirm" :modal="true">
<div class="confirmation-content">
<i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" />
<span v-if="product">Are you sure you want to delete the selected products?</span>
</div>
<template #footer>
<Button label="No" icon="pi pi-times" class="p-button-text" @click="deleteProductsDialog = false" />
<Button label="Yes" icon="pi pi-check" class="p-button-text" @click="deleteSelectedProducts" />
</template>
</Dialog>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
import { FilterMatchMode } from 'primevue/api';
export default {
data() {
return {
products: null,
productDialog: false,
deleteProductDialog: false,
deleteProductsDialog: false,
product: {},
selectedProducts: null,
filters: {},
submitted: false,
statuses: [
{ label: 'INSTOCK', value: 'instock' },
{ label: 'LOWSTOCK', value: 'lowstock' },
{ label: 'OUTOFSTOCK', value: 'outofstock' }
],
code: {
basic: `
<Toolbar class="mb-4">
<template #start>
<Button label="New" icon="pi pi-plus" class="p-button-success mr-2" @click="openNew" />
<Button label="Delete" icon="pi pi-trash" class="p-button-danger" @click="confirmDeleteSelected" :disabled="!selectedProducts || !selectedProducts.length" />
</template>
<template #end>
<FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import" chooseLabel="Import" class="mr-2 inline-block" />
<Button label="Export" icon="pi pi-upload" class="p-button-help" @click="exportCSV($event)" />
</template>
</Toolbar>
<DataTable ref="dt" :value="products" v-model:selection="selectedProducts" dataKey="id"
:paginator="true" :rows="10" :filters="filters"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" :rowsPerPageOptions="[5,10,25]"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products">
<template #header>
<div class="flex flex-wrap gap-2 align-items-center justify-content-between">
<h4 class="m-0">Manage Products</h4>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Search..." />
</span>
</div>
</template>
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
<Column field="code" header="Code" sortable style="min-width:12rem"></Column>
<Column field="name" header="Name" sortable style="min-width:16rem"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-2 border-round" style="width: 64px" />
</template>
</Column>
<Column field="price" header="Price" sortable style="min-width:8rem">
<template #body="slotProps">
{{formatCurrency(slotProps.data.price)}}
</template>
</Column>
<Column field="category" header="Category" sortable style="min-width:10rem"></Column>
<Column field="rating" header="Reviews" sortable style="min-width:12rem">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" />
</template>
</Column>
<Column field="inventoryStatus" header="Status" sortable style="min-width:12rem">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" />
</template>
</Column>
<Column :exportable="false" style="min-width:8rem">
<template #body="slotProps">
<Button icon="pi pi-pencil" class="p-button-icon-only p-button-outlined p-button-rounded mr-2" @click="editProduct(slotProps.data)" />
<Button icon="pi pi-trash" class="p-button-icon-only p-button-outlined p-button-rounded p-button-danger" @click="confirmDeleteProduct(slotProps.data)" />
</template>
</Column>
</DataTable>`,
options: `
<template>
<div>
<div class="card">
<Toolbar class="mb-4">
<template #start>
<Button label="New" icon="pi pi-plus" class="p-button-success mr-2" @click="openNew" />
<Button label="Delete" icon="pi pi-trash" class="p-button-danger" @click="confirmDeleteSelected" :disabled="!selectedProducts || !selectedProducts.length" />
</template>
<template #end>
<FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import" chooseLabel="Import" class="mr-2 inline-block" />
<Button label="Export" icon="pi pi-upload" class="p-button-help" @click="exportCSV($event)" />
</template>
</Toolbar>
<DataTable ref="dt" :value="products" v-model:selection="selectedProducts" dataKey="id"
:paginator="true" :rows="10" :filters="filters"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" :rowsPerPageOptions="[5,10,25]"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products">
<template #header>
<div class="flex flex-wrap gap-2 align-items-center justify-content-between">
<h4 class="m-0">Manage Products</h4>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Search..." />
</span>
</div>
</template>
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
<Column field="code" header="Code" sortable style="min-width:12rem"></Column>
<Column field="name" header="Name" sortable style="min-width:16rem"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-2 border-round" style="width: 64px" />
</template>
</Column>
<Column field="price" header="Price" sortable style="min-width:8rem">
<template #body="slotProps">
{{formatCurrency(slotProps.data.price)}}
</template>
</Column>
<Column field="category" header="Category" sortable style="min-width:10rem"></Column>
<Column field="rating" header="Reviews" sortable style="min-width:12rem">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" />
</template>
</Column>
<Column field="inventoryStatus" header="Status" sortable style="min-width:12rem">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" />
</template>
</Column>
<Column :exportable="false" style="min-width:8rem">
<template #body="slotProps">
<Button icon="pi pi-pencil" class="p-button-icon-only p-button-outlined p-button-rounded mr-2" @click="editProduct(slotProps.data)" />
<Button icon="pi pi-trash" class="p-button-icon-only p-button-outlined p-button-rounded p-button-danger" @click="confirmDeleteProduct(slotProps.data)" />
</template>
</Column>
</DataTable>
</div>
<Dialog v-model:visible="productDialog" :style="{width: '450px'}" header="Product Details" :modal="true" class="p-fluid">
<img v-if="product.image" :src="\`https://primefaces.org/cdn/primevue/images/product/\${product.image}\`" :alt="product.image" class="block m-auto pb-3" />
<div class="field">
<label for="name">Name</label>
<InputText id="name" v-model.trim="product.name" required="true" autofocus :class="{'p-invalid': submitted && !product.name}" />
<small class="p-error" v-if="submitted && !product.name">Name is required.</small>
</div>
<div class="field">
<label for="description">Description</label>
<Textarea id="description" v-model="product.description" required="true" rows="3" cols="20" />
</div>
<div class="field">
<label for="inventoryStatus" class="mb-3">Inventory Status</label>
<Dropdown id="inventoryStatus" v-model="product.inventoryStatus" :options="statuses" optionLabel="label" placeholder="Select a Status">
<template #value="slotProps">
<div v-if="slotProps.value && slotProps.value.value">
<Tag :value="slotProps.value.value" :severity="getStatusLabel(slotProps.value.label)" />
</div>
<div v-else-if="slotProps.value && !slotProps.value.value">
<Tag :value="slotProps.value" :severity="getStatusLabel(slotProps.value)" />
</div>
<span v-else>
{{slotProps.placeholder}}
</span>
</template>
</Dropdown>
</div>
<div class="field">
<label class="mb-3">Category</label>
<div class="formgrid grid">
<div class="field-radiobutton col-6">
<RadioButton id="category1" name="category" value="Accessories" v-model="product.category" />
<label for="category1">Accessories</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category2" name="category" value="Clothing" v-model="product.category" />
<label for="category2">Clothing</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category3" name="category" value="Electronics" v-model="product.category" />
<label for="category3">Electronics</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category4" name="category" value="Fitness" v-model="product.category" />
<label for="category4">Fitness</label>
</div>
</div>
</div>
<div class="formgrid grid">
<div class="field col">
<label for="price">Price</label>
<InputNumber id="price" v-model="product.price" mode="currency" currency="USD" locale="en-US" />
</div>
<div class="field col">
<label for="quantity">Quantity</label>
<InputNumber id="quantity" v-model="product.quantity" integeronly />
</div>
</div>
<template #footer>
<Button label="Cancel" icon="pi pi-times" class="p-button-text" @click="hideDialog"/>
<Button label="Save" icon="pi pi-check" class="p-button-text" @click="saveProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductDialog" :style="{width: '450px'}" header="Confirm" :modal="true">
<div class="confirmation-content">
<i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" />
<span v-if="product">Are you sure you want to delete <b>{{product.name}}</b>?</span>
</div>
<template #footer>
<Button label="No" icon="pi pi-times" class="p-button-text" @click="deleteProductDialog = false"/>
<Button label="Yes" icon="pi pi-check" class="p-button-text" @click="deleteProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductsDialog" :style="{width: '450px'}" header="Confirm" :modal="true">
<div class="confirmation-content">
<i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" />
<span v-if="product">Are you sure you want to delete the selected products?</span>
</div>
<template #footer>
<Button label="No" icon="pi pi-times" class="p-button-text" @click="deleteProductsDialog = false"/>
<Button label="Yes" icon="pi pi-check" class="p-button-text" @click="deleteSelectedProducts" />
</template>
</Dialog>
</div>
</template>
<script>
import { FilterMatchMode } from 'primevue/api';
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
productDialog: false,
deleteProductDialog: false,
deleteProductsDialog: false,
product: {},
selectedProducts: null,
filters: {},
submitted: false,
statuses: [
{label: 'INSTOCK', value: 'instock'},
{label: 'LOWSTOCK', value: 'lowstock'},
{label: 'OUTOFSTOCK', value: 'outofstock'}
]
}
},
created() {
this.initFilters();
},
mounted() {
ProductService.getProducts().then((data) => (this.products = data));
},
methods: {
formatCurrency(value) {
if(value)
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
return;
},
openNew() {
this.product = {};
this.submitted = false;
this.productDialog = true;
},
hideDialog() {
this.productDialog = false;
this.submitted = false;
},
saveProduct() {
this.submitted = true;
if (this.product.name.trim()) {
if (this.product.id) {
this.product.inventoryStatus = this.product.inventoryStatus.value ? this.product.inventoryStatus.value: this.product.inventoryStatus;
this.products[this.findIndexById(this.product.id)] = this.product;
this.$toast.add({severity:'success', summary: 'Successful', detail: 'Product Updated', life: 3000});
}
else {
this.product.id = this.createId();
this.product.code = this.createId();
this.product.image = 'product-placeholder.svg';
this.product.inventoryStatus = this.product.inventoryStatus ? this.product.inventoryStatus.value : 'INSTOCK';
this.products.push(this.product);
this.$toast.add({severity:'success', summary: 'Successful', detail: 'Product Created', life: 3000});
}
this.productDialog = false;
this.product = {};
}
},
editProduct(product) {
this.product = {...product};
this.productDialog = true;
},
confirmDeleteProduct(product) {
this.product = product;
this.deleteProductDialog = true;
},
deleteProduct() {
this.products = this.products.filter(val => val.id !== this.product.id);
this.deleteProductDialog = false;
this.product = {};
this.$toast.add({severity:'success', summary: 'Successful', detail: 'Product Deleted', life: 3000});
},
findIndexById(id) {
let index = -1;
for (let i = 0; i < this.products.length; i++) {
if (this.products[i].id === id) {
index = i;
break;
}
}
return index;
},
createId() {
let id = '';
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for ( var i = 0; i < 5; i++ ) {
id += chars.charAt(Math.floor(Math.random() * chars.length));
}
return id;
},
exportCSV() {
this.$refs.dt.exportCSV();
},
confirmDeleteSelected() {
this.deleteProductsDialog = true;
},
deleteSelectedProducts() {
this.products = this.products.filter(val => !this.selectedProducts.includes(val));
this.deleteProductsDialog = false;
this.selectedProducts = null;
this.$toast.add({severity:'success', summary: 'Successful', detail: 'Products Deleted', life: 3000});
},
initFilters() {
this.filters = {
'global': {value: null, matchMode: FilterMatchMode.CONTAINS},
}
}
}
}
<\/script>
`,
composition: `
<template>
<div>
<div class="card">
<Toolbar class="mb-4">
<template #start>
<Button label="New" icon="pi pi-plus" class="p-button-success mr-2" @click="openNew" />
<Button label="Delete" icon="pi pi-trash" class="p-button-danger" @click="confirmDeleteSelected" :disabled="!selectedProducts || !selectedProducts.length" />
</template>
<template #end>
<FileUpload mode="basic" accept="image/*" :maxFileSize="1000000" label="Import" chooseLabel="Import" class="mr-2 inline-block" />
<Button label="Export" icon="pi pi-upload" class="p-button-help" @click="exportCSV($event)" />
</template>
</Toolbar>
<DataTable ref="dt" :value="products" v-model:selection="selectedProducts" dataKey="id"
:paginator="true" :rows="10" :filters="filters"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown" :rowsPerPageOptions="[5,10,25]"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products">
<template #header>
<div class="flex flex-wrap gap-2 align-items-center justify-content-between">
<h4 class="m-0">Manage Products</h4>
<span class="p-input-icon-left">
<i class="pi pi-search" />
<InputText v-model="filters['global'].value" placeholder="Search..." />
</span>
</div>
</template>
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
<Column field="code" header="Code" sortable style="min-width:12rem"></Column>
<Column field="name" header="Name" sortable style="min-width:16rem"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="\`https://primefaces.org/cdn/primevue/images/product/\${slotProps.data.image}\`" :alt="slotProps.data.image" class="shadow-2 border-round" style="width: 64px" />
</template>
</Column>
<Column field="price" header="Price" sortable style="min-width:8rem">
<template #body="slotProps">
{{formatCurrency(slotProps.data.price)}}
</template>
</Column>
<Column field="category" header="Category" sortable style="min-width:10rem"></Column>
<Column field="rating" header="Reviews" sortable style="min-width:12rem">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" :readonly="true" :cancel="false" />
</template>
</Column>
<Column field="inventoryStatus" header="Status" sortable style="min-width:12rem">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getStatusLabel(slotProps.data.inventoryStatus)" />
</template>
</Column>
<Column :exportable="false" style="min-width:8rem">
<template #body="slotProps">
<Button icon="pi pi-pencil" class="p-button-icon-only p-button-outlined p-button-rounded mr-2" @click="editProduct(slotProps.data)" />
<Button icon="pi pi-trash" class="p-button-icon-only p-button-outlined p-button-rounded p-button-danger" @click="confirmDeleteProduct(slotProps.data)" />
</template>
</Column>
</DataTable>
</div>
<Dialog v-model:visible="productDialog" :style="{width: '450px'}" header="Product Details" :modal="true" class="p-fluid">
<img v-if="product.image" :src="\`https://primefaces.org/cdn/primevue/images/product/\${product.image}\`" :alt="product.image" class="block m-auto pb-3" />
<div class="field">
<label for="name">Name</label>
<InputText id="name" v-model.trim="product.name" required="true" autofocus :class="{'p-invalid': submitted && !product.name}" />
<small class="p-error" v-if="submitted && !product.name">Name is required.</small>
</div>
<div class="field">
<label for="description">Description</label>
<Textarea id="description" v-model="product.description" required="true" rows="3" cols="20" />
</div>
<div class="field">
<label for="inventoryStatus" class="mb-3">Inventory Status</label>
<Dropdown id="inventoryStatus" v-model="product.inventoryStatus" :options="statuses" optionLabel="label" placeholder="Select a Status">
<template #value="slotProps">
<div v-if="slotProps.value && slotProps.value.value">
<Tag :value="slotProps.value.value" :severity="getStatusLabel(slotProps.value.label)" />
</div>
<div v-else-if="slotProps.value && !slotProps.value.value">
<Tag :value="slotProps.value" :severity="getStatusLabel(slotProps.value)" />
</div>
<span v-else>
{{slotProps.placeholder}}
</span>
</template>
</Dropdown>
</div>
<div class="field">
<label class="mb-3">Category</label>
<div class="formgrid grid">
<div class="field-radiobutton col-6">
<RadioButton id="category1" name="category" value="Accessories" v-model="product.category" />
<label for="category1">Accessories</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category2" name="category" value="Clothing" v-model="product.category" />
<label for="category2">Clothing</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category3" name="category" value="Electronics" v-model="product.category" />
<label for="category3">Electronics</label>
</div>
<div class="field-radiobutton col-6">
<RadioButton id="category4" name="category" value="Fitness" v-model="product.category" />
<label for="category4">Fitness</label>
</div>
</div>
</div>
<div class="formgrid grid">
<div class="field col">
<label for="price">Price</label>
<InputNumber id="price" v-model="product.price" mode="currency" currency="USD" locale="en-US" />
</div>
<div class="field col">
<label for="quantity">Quantity</label>
<InputNumber id="quantity" v-model="product.quantity" integeronly />
</div>
</div>
<template #footer>
<Button label="Cancel" icon="pi pi-times" class="p-button-text" @click="hideDialog"/>
<Button label="Save" icon="pi pi-check" class="p-button-text" @click="saveProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductDialog" :style="{width: '450px'}" header="Confirm" :modal="true">
<div class="confirmation-content">
<i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" />
<span v-if="product">Are you sure you want to delete <b>{{product.name}}</b>?</span>
</div>
<template #footer>
<Button label="No" icon="pi pi-times" class="p-button-text" @click="deleteProductDialog = false"/>
<Button label="Yes" icon="pi pi-check" class="p-button-text" @click="deleteProduct" />
</template>
</Dialog>
<Dialog v-model:visible="deleteProductsDialog" :style="{width: '450px'}" header="Confirm" :modal="true">
<div class="confirmation-content">
<i class="pi pi-exclamation-triangle mr-3" style="font-size: 2rem" />
<span v-if="product">Are you sure you want to delete the selected products?</span>
</div>
<template #footer>
<Button label="No" icon="pi pi-times" class="p-button-text" @click="deleteProductsDialog = false"/>
<Button label="Yes" icon="pi pi-check" class="p-button-text" @click="deleteSelectedProducts" />
</template>
</Dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { FilterMatchMode } from 'primevue/api';
import { useToast } from 'primevue/usetoast';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProducts().then((data) => (products.value = data));
});
const toast = useToast();
const dt = ref();
const products = ref();
const productDialog = ref(false);
const deleteProductDialog = ref(false);
const deleteProductsDialog = ref(false);
const product = ref({});
const selectedProducts = ref();
const filters = ref({
'global': {value: null, matchMode: FilterMatchMode.CONTAINS},
});
const submitted = ref(false);
const statuses = ref([
{label: 'INSTOCK', value: 'instock'},
{label: 'LOWSTOCK', value: 'lowstock'},
{label: 'OUTOFSTOCK', value: 'outofstock'}
]);
const formatCurrency = (value) => {
if(value)
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
return;
};
const openNew = () => {
product.value = {};
submitted.value = false;
productDialog.value = true;
};
const hideDialog = () => {
productDialog.value = false;
submitted.value = false;
};
const saveProduct = () => {
submitted.value = true;
if (product.value.name.trim()) {
if (product.value.id) {
product.value.inventoryStatus = product.value.inventoryStatus.value ? product.value.inventoryStatus.value : product.value.inventoryStatus;
products.value[findIndexById(product.value.id)] = product.value;
toast.add({severity:'success', summary: 'Successful', detail: 'Product Updated', life: 3000});
}
else {
product.value.id = createId();
product.value.code = createId();
product.value.image = 'product-placeholder.svg';
product.value.inventoryStatus = product.value.inventoryStatus ? product.value.inventoryStatus.value : 'INSTOCK';
products.value.push(product.value);
toast.add({severity:'success', summary: 'Successful', detail: 'Product Created', life: 3000});
}
productDialog.value = false;
product.value = {};
}
};
const editProduct = (prod) => {
product.value = {...prod};
productDialog.value = true;
};
const confirmDeleteProduct = (prod) => {
product.value = prod;
deleteProductDialog.value = true;
};
const deleteProduct = () => {
products.value = products.value.filter(val => val.id !== product.value.id);
deleteProductDialog.value = false;
product.value = {};
toast.add({severity:'success', summary: 'Successful', detail: 'Product Deleted', life: 3000});
};
const findIndexById = (id) => {
let index = -1;
for (let i = 0; i < products.value.length; i++) {
if (products.value[i].id === id) {
index = i;
break;
}
}
return index;
};
const createId = () => {
let id = '';
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for ( var i = 0; i < 5; i++ ) {
id += chars.charAt(Math.floor(Math.random() * chars.length));
}
return id;
}
const exportCSV = () => {
dt.value.exportCSV();
};
const confirmDeleteSelected = () => {
deleteProductsDialog.value = true;
};
const deleteSelectedProducts = () => {
products.value = products.value.filter(val => !selectedProducts.value.includes(val));
deleteProductsDialog.value = false;
selectedProducts.value = null;
toast.add({severity:'success', summary: 'Successful', detail: 'Products Deleted', life: 3000});
};
<\/script>
`
}
};
},
created() {
this.initFilters();
},
mounted() {
ProductService.getProducts().then((data) => (this.products = data));
},
methods: {
formatCurrency(value) {
if (value) return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
return;
},
openNew() {
this.product = {};
this.submitted = false;
this.productDialog = true;
},
hideDialog() {
this.productDialog = false;
this.submitted = false;
},
saveProduct() {
this.submitted = true;
if (this.product.name.trim()) {
if (this.product.id) {
this.product.inventoryStatus = this.product.inventoryStatus.value ? this.product.inventoryStatus.value : this.product.inventoryStatus;
this.products[this.findIndexById(this.product.id)] = this.product;
this.$toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Updated', life: 3000 });
} else {
this.product.id = this.createId();
this.product.code = this.createId();
this.product.image = 'product-placeholder.svg';
this.product.inventoryStatus = this.product.inventoryStatus ? this.product.inventoryStatus.value : 'INSTOCK';
this.products.push(this.product);
this.$toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Created', life: 3000 });
}
this.productDialog = false;
this.product = {};
}
},
editProduct(product) {
this.product = { ...product };
this.productDialog = true;
},
confirmDeleteProduct(product) {
this.product = product;
this.deleteProductDialog = true;
},
deleteProduct() {
this.products = this.products.filter((val) => val.id !== this.product.id);
this.deleteProductDialog = false;
this.product = {};
this.$toast.add({ severity: 'success', summary: 'Successful', detail: 'Product Deleted', life: 3000 });
},
findIndexById(id) {
let index = -1;
for (let i = 0; i < this.products.length; i++) {
if (this.products[i].id === id) {
index = i;
break;
}
}
return index;
},
createId() {
let id = '';
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < 5; i++) {
id += chars.charAt(Math.floor(Math.random() * chars.length));
}
return id;
},
exportCSV() {
this.$refs.dt.exportCSV();
},
confirmDeleteSelected() {
this.deleteProductsDialog = true;
},
deleteSelectedProducts() {
this.products = this.products.filter((val) => !this.selectedProducts.includes(val));
this.deleteProductsDialog = false;
this.selectedProducts = null;
this.$toast.add({ severity: 'success', summary: 'Successful', detail: 'Products Deleted', life: 3000 });
},
initFilters() {
this.filters = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS }
};
},
getStatusLabel(status) {
switch (status) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warning';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
}
}
};
</script>

View file

@ -0,0 +1,146 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Flex scroll feature makes the scrollable viewport section dynamic instead of a fixed value so that it can grow or shrink relative to the parent size of the table. Click the button below to display a maximizable Dialog where data viewport
adjusts itself according to the size changes.
</p>
</DocSectionText>
<div class="card flex justify-content-center">
<Button label="Show" icon="pi pi-external-link" @click="dialogVisible = true" />
<Dialog v-model:visible="dialogVisible" header="Flex Scroll" :style="{ width: '75vw' }" maximizable modal :contentStyle="{ height: '300px' }">
<DataTable :value="customers" scrollable scrollHeight="flex" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
<template #footer>
<Button label="Ok" icon="pi pi-check" @click="dialogVisible = false" />
</template>
</Dialog>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
dialogVisible: false,
code: {
basic: `
<Button label="Show" icon="pi pi-external-link" @click="dialogVisible = true" />
<Dialog v-model:visible="dialogVisible" header="Flex Scroll" :style="{ width: '75vw' }" maximizable modal :contentStyle="{ height: '300px' }">
<DataTable :value="customers" scrollable scrollHeight="flex" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
<template #footer>
<Button label="Ok" icon="pi pi-check" @click="dialogVisible = false" />
</template>
</Dialog>`,
options: `
<template>
<div class="card">
<Button label="Show" icon="pi pi-external-link" @click="dialogVisible = true" />
<Dialog v-model:visible="dialogVisible" header="Flex Scroll" :style="{ width: '75vw' }" maximizable modal :contentStyle="{ height: '300px' }">
<DataTable :value="customers" scrollable scrollHeight="flex" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
<template #footer>
<Button label="Ok" icon="pi pi-check" @click="dialogVisible = false" />
</template>
</Dialog>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
dialogVisible: false
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = data;
});
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<Button label="Show" icon="pi pi-external-link" @click="dialogVisible = true" />
<Dialog v-model:visible="dialogVisible" header="Flex Scroll" :style="{ width: '75vw' }" maximizable modal :contentStyle="{ height: '300px' }">
<DataTable :value="customers" scrollable scrollHeight="flex" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
<template #footer>
<Button label="Ok" icon="pi pi-check" @click="dialogVisible = false" />
</template>
</Dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
const customers = ref();
const dialogVisible = ref(false);
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => {
customers.value = data;
});
});
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...
`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = data;
});
}
};
</script>

View file

@ -0,0 +1,176 @@
<template>
<DocSectionText v-bind="$attrs">
<p>A column can be fixed during horizontal scrolling by enabling the <i>frozen</i> property. The location is defined with the <i>alignFrozen</i> that can be <i>left</i> or <i>right</i>.</p>
</DocSectionText>
<div class="card">
<ToggleButton v-model="balanceFrozen" onIcon="pi pi-lock" offIcon="pi pi-lock-open" onLabel="Balance" offLabel="Balance" />
<DataTable :value="customers" scrollable scrollHeight="400px" class="mt-4">
<Column field="name" header="Name" style="min-width: 200px" frozen class="font-bold"></Column>
<Column field="id" header="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" style="min-width: 200px" alignFrozen="right" :frozen="balanceFrozen">
<template #body="{ data }">
<span class="font-bold">{{ formatCurrency(data.balance) }}</span>
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
balanceFrozen: false,
code: {
basic: `
<ToggleButton v-model="balanceFrozen" onIcon="pi pi-lock" offIcon="pi pi-lock-open" onLabel="Balance" offLabel="Balance" />
<DataTable :value="customers" scrollable scrollHeight="400px" class="mt-4">
<Column field="name" header="Name" style="min-width: 200px" frozen class="font-bold"></Column>
<Column field="id" header="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" style="min-width: 200px" alignFrozen="right" :frozen="balanceFrozen">
<template #body="{ data }">
<span class="font-bold">{{ formatCurrency(data.balance) }}</span>
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card">
<ToggleButton v-model="balanceFrozen" onIcon="pi pi-lock" offIcon="pi pi-lock-open" onLabel="Balance" offLabel="Balance" />
<DataTable :value="customers" scrollable scrollHeight="400px" class="mt-4">
<Column field="name" header="Name" style="min-width: 200px" frozen class="font-bold"></Column>
<Column field="id" header="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" style="min-width: 200px" alignFrozen="right" :frozen="balanceFrozen">
<template #body="{ data }">
<span class="font-bold">{{ formatCurrency(data.balance) }}</span>
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
balanceFrozen: false
};
},
mounted() {
CustomerService.getCustomersLarge().then((data) => {
this.customers = data;
});
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<ToggleButton v-model="balanceFrozen" onIcon="pi pi-lock" offIcon="pi pi-lock-open" onLabel="Balance" offLabel="Balance" />
<DataTable :value="customers" scrollable scrollHeight="400px" class="mt-4">
<Column field="name" header="Name" style="min-width: 200px" frozen class="font-bold"></Column>
<Column field="id" header="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" style="min-width: 200px" alignFrozen="right" :frozen="balanceFrozen">
<template #body="{ data }">
<span class="font-bold">{{ formatCurrency(data.balance) }}</span>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
onMounted(() => {
CustomerService.getCustomersLarge().then((data) => {
customers.value = data;
});
});
const customers = ref();
const balanceFrozen = ref(false);
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...
`
}
};
},
mounted() {
CustomerService.getCustomersLarge().then((data) => {
this.customers = data;
});
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
}
}
};
</script>

View file

@ -0,0 +1,232 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Rows can be fixed during scrolling by enabling the <i>frozenValue</i> property.</p>
</DocSectionText>
<div class="card">
<DataTable :value="customers" :frozenValue="lockedCustomers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="status" header="Status"></Column>
<Column style="flex: 0 0 4rem">
<template #body="{ data, frozenRow, index }">
<Button type="button" :icon="frozenRow ? 'pi pi-lock-open' : 'pi pi-lock'" :disabled="frozenRow ? false : lockedCustomers.length >= 2" class="p-button-sm p-button-text" @click="toggleLock(data, frozenRow, index)" />
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
lockedCustomers: [
{
id: 5135,
name: 'Geraldine Bisset',
country: {
name: 'France',
code: 'fr'
},
company: 'Bisset Group',
status: 'proposal',
date: '2019-05-05',
activity: 0,
representative: {
name: 'Amy Elsner',
image: 'amyelsner.png'
}
}
],
code: {
basic: `
<DataTable :value="customers" :frozenValue="lockedCustomers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="status" header="Status"></Column>
<Column style="flex: 0 0 4rem">
<template #body="{ data, frozenRow, index }">
<Button type="button" :icon="frozenRow ? 'pi pi-lock-open' : 'pi pi-lock'" :disabled="frozenRow ? false : lockedCustomers.length >= 2" class="p-button-sm p-button-text" @click="toggleLock(data, frozenRow, index)" />
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="customers" :frozenValue="lockedCustomers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="status" header="Status"></Column>
<Column style="flex: 0 0 4rem">
<template #body="{ data, frozenRow, index }">
<Button type="button" :icon="frozenRow ? 'pi pi-lock-open' : 'pi pi-lock'" :disabled="frozenRow ? false : lockedCustomers.length >= 2" class="p-button-sm p-button-text" @click="toggleLock(data, frozenRow, index)" />
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
lockedCustomers: [
{
id: 5135,
name: 'Geraldine Bisset',
country: {
name: 'France',
code: 'fr'
},
company: 'Bisset Group',
status: 'proposal',
date: '2019-05-05',
activity: 0,
representative: {
name: 'Amy Elsner',
image: 'amyelsner.png'
}
}
]
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = data;
});
},
methods: {
toggleLock(data, frozen, index) {
if (frozen) {
this.lockedCustomers = this.lockedCustomers.filter((c, i) => i !== index);
this.customers.push(data);
} else {
this.customers = this.customers.filter((c, i) => i !== index);
this.lockedCustomers.push(data);
}
this.customers.sort((val1, val2) => {
return val1.id < val2.id ? -1 : 1;
});
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="customers" :frozenValue="lockedCustomers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="status" header="Status"></Column>
<Column style="flex: 0 0 4rem">
<template #body="{ data, frozenRow, index }">
<Button type="button" :icon="frozenRow ? 'pi pi-lock-open' : 'pi pi-lock'" :disabled="frozenRow ? false : lockedCustomers.length >= 2" class="p-button-sm p-button-text" @click="toggleLock(data, frozenRow, index)" />
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
const customers = ref();
const lockedCustomers = ref([
{
id: 5135,
name: 'Geraldine Bisset',
country: {
name: 'France',
code: 'fr'
},
company: 'Bisset Group',
status: 'proposal',
date: '2019-05-05',
activity: 0,
representative: {
name: 'Amy Elsner',
image: 'amyelsner.png'
}
}
]);
const toggleLock = (data, frozen, index) => {
if (frozen) {
lockedCustomers.value = lockedCustomers.value.filter((c, i) => i !== index);
customers.value.push(data);
}
else {
customers.value = customers.value.filter((c, i) => i !== index);
lockedCustomers.value.push(data);
}
customers.value.sort((val1, val2) => {
return val1.id < val2.id ? -1 : 1;
});
};
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => {
customers.value = data;
});
});
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...
`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = data;
});
},
methods: {
toggleLock(data, frozen, index) {
if (frozen) {
this.lockedCustomers = this.lockedCustomers.filter((c, i) => i !== index);
this.customers.push(data);
} else {
this.customers = this.customers.filter((c, i) => i !== index);
this.lockedCustomers.push(data);
}
this.customers.sort((val1, val2) => {
return val1.id < val2.id ? -1 : 1;
});
}
}
};
</script>

View file

@ -0,0 +1,163 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Horizontal scrollbar is displayed when table width exceeds the parent width.</p>
</DocSectionText>
<div class="card">
<DataTable :value="customers" scrollable scrollHeight="400px">
<Column field="id" header="Id" footer="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" footer="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" footer="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" footer="Date" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" footer="Balance" style="min-width: 200px">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
</Column>
<Column field="company" header="Company" footer="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" footer="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" footer="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" footer="Representative" style="min-width: 200px"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
code: {
basic: `
<DataTable :value="customers" scrollable scrollHeight="400px">
<Column field="id" header="Id" footer="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" footer="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" footer="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" footer="Date" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" footer="Balance" style="min-width: 200px">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
</Column>
<Column field="company" header="Company" footer="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" footer="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" footer="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" footer="Representative" style="min-width: 200px"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="customers" scrollable scrollHeight="400px">
<Column field="id" header="Id" footer="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" footer="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" footer="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" footer="Date" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" footer="Balance" style="min-width: 200px">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
</Column>
<Column field="company" header="Company" footer="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" footer="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" footer="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" footer="Representative" style="min-width: 200px"></Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = data;
});
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="customers" scrollable scrollHeight="400px">
<Column field="id" header="Id" footer="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" footer="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" footer="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" footer="Date" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" footer="Balance" style="min-width: 200px">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
</Column>
<Column field="company" header="Company" footer="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" footer="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" footer="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" footer="Representative" style="min-width: 200px"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
const customers = ref();
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => {
customers.value = data;
});
});
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...
`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = data;
});
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
}
}
};
</script>

View file

@ -0,0 +1,113 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Adding <i>scrollable</i> property along with a <i>scrollHeight</i> for the data viewport enables vertical scrolling with fixed headers.</p>
</DocSectionText>
<div class="card">
<DataTable :value="customers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CustomerService']" />
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null,
code: {
basic: `
<DataTable :value="customers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="customers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
</div>
</template>
<script>
import { CustomerService } from '@/service/CustomerService';
export default {
data() {
return {
customers: null
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = data;
});
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="customers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CustomerService } from '@/service/CustomerService';
const customers = ref();
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => {
customers.value = data;
});
});
<\/script>`,
data: `
{
id: 1000,
name: 'James Butt',
country: {
name: 'Algeria',
code: 'dz'
},
company: 'Benton, John B Jr',
date: '2015-09-13',
status: 'unqualified',
verified: true,
activity: 17,
representative: {
name: 'Ioni Bowcher',
image: 'ionibowcher.png'
},
balance: 70663
},
...
`
}
};
},
mounted() {
CustomerService.getCustomersMedium().then((data) => {
this.customers = data;
});
}
};
</script>

View file

@ -0,0 +1,102 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Multiple columns can be sorted by defining <i>sortMode</i> as <i>multiple</i>. This mode requires metaKey (e.g. <i></i>) to be pressed when clicking a header.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" sortMode="multiple" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" sortMode="multiple" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" sortMode="multiple" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" sortMode="multiple" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,138 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Defining a default <i>sortField</i> and <i>sortOrder</i> displays data as sorted initially in single column sorting. In <i>multiple</i> sort mode, <i>multiSortMeta</i> should be used instead by providing an array of
<i>DataTableSortMeta</i> objects.
</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" sortField="price" :sortOrder="-1" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 20%"></Column>
<Column field="name" header="Name" sortable style="width: 20%"></Column>
<Column field="price" header="Price" :sortable="true">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category" sortable style="width: 20%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 20%"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" sortField="price" :sortOrder="-1" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 20%"></Column>
<Column field="name" header="Name" sortable style="width: 20%"></Column>
<Column field="price" header="Price" :sortable="true">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category" sortable style="width: 20%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 20%"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" sortField="price" :sortOrder="-1" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 20%"></Column>
<Column field="name" header="Name" sortable style="width: 20%"></Column>
<Column field="price" header="Price" :sortable="true">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category" sortable style="width: 20%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 20%"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" sortField="price" :sortOrder="-1" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 20%"></Column>
<Column field="name" header="Name" sortable style="width: 20%"></Column>
<Column field="price" header="Price" :sortable="true">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category" sortable style="width: 20%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 20%"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
const formatCurrency = (value) => {
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
}
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
},
methods: {
formatCurrency(value) {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
}
}
};
</script>

View file

@ -0,0 +1,102 @@
<template>
<DocSectionText v-bind="$attrs">
<p>When <i>removableSort</i> is present, the third click removes the sorting from the column.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" removableSort tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" removableSort tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" removableSort tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" removableSort tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,102 @@
<template>
<DocSectionText v-bind="$attrs">
<p>Sorting on a column is enabled by adding the <i>sortable</i> property.</p>
</DocSectionText>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['ProductService']" />
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null,
code: {
basic: `
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script>
import { ProductService } from '@/service/ProductService';
export default {
data() {
return {
products: null
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="products" tableStyle="min-width: 50rem">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ProductService } from '@/service/ProductService';
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
const products = ref();
<\/script>`,
data: `
{
id: '1000',
code: 'f230fh0g3',
name: 'Bamboo Watch',
description: 'Product Description',
image: 'bamboo-watch.jpg',
price: 65,
category: 'Accessories',
quantity: 24,
inventoryStatus: 'INSTOCK',
rating: 5
},
...
`
}
};
},
mounted() {
ProductService.getProductsMini().then((data) => (this.products = data));
}
};
</script>

View file

@ -0,0 +1,300 @@
<template>
<DocSectionText v-bind="$attrs">
<p>When lazy loading is enabled via the <i>virtualScrollerOptions</i>, data is fetched on demand during scrolling instead of preload.</p>
<p>In sample below, an in-memory list and timeout is used to mimic fetching from a remote datasource. The <i>virtualCars</i> is an empty array that is populated on scroll.</p>
</DocSectionText>
<div class="card">
<DataTable
:value="virtualCars"
scrollable
scrollHeight="400px"
:virtualScrollerOptions="{ lazy: true, onLazyLoad: loadCarsLazy, itemSize: 46, delay: 200, showLoader: true, loading: lazyLoading, numToleratedItems: 10 }"
tableStyle="min-width: 50rem"
>
<Column field="id" header="Id" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
<Column field="vin" header="Vin" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="year" header="Year" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="30%" height="1rem" />
</div>
</template>
</Column>
<Column field="brand" header="Brand" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="color" header="Color" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CarService']" />
</template>
<script>
import { CarService } from '@/service/CarService';
export default {
data() {
return {
cars: null,
virtualCars: Array.from({ length: 100000 }),
lazyLoading: false,
loadLazyTimeout: null,
code: {
basic: `
<DataTable :value="virtualCars" scrollable scrollHeight="400px" tableStyle="min-width: 50rem"
:virtualScrollerOptions="{ lazy: true, onLazyLoad: loadCarsLazy, itemSize: 46, delay: 200, showLoader: true, loading: lazyLoading, numToleratedItems: 10 }">
<Column field="id" header="Id" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
<Column field="vin" header="Vin" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="year" header="Year" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="30%" height="1rem" />
</div>
</template>
</Column>
<Column field="brand" header="Brand" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="color" header="Color" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="virtualCars" scrollable scrollHeight="400px" tableStyle="min-width: 50rem"
:virtualScrollerOptions="{ lazy: true, onLazyLoad: loadCarsLazy, itemSize: 46, delay: 200, showLoader: true, loading: lazyLoading, numToleratedItems: 10 }">
<Column field="id" header="Id" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
<Column field="vin" header="Vin" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="year" header="Year" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="30%" height="1rem" />
</div>
</template>
</Column>
<Column field="brand" header="Brand" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="color" header="Color" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
</DataTable>
</div>
</template>
<script>
import { CarService } from '@/service/CarService';
export default {
data() {
return {
cars: null,
virtualCars: Array.from({ length: 100000 }),
lazyLoading: false,
loadLazyTimeout: null
};
},
mounted() {
this.cars = Array.from({ length: 100000 }).map((_, i) => CarService.generateCar(i + 1));
},
methods: {
loadCarsLazy(event) {
!this.lazyLoading && (this.lazyLoading = true);
if (this.loadLazyTimeout) {
clearTimeout(this.loadLazyTimeout);
}
//simulate remote connection with a timeout
this.loadLazyTimeout = setTimeout(() => {
let _virtualCars = [...this.virtualCars];
let { first, last } = event;
//load data of required page
const loadedCars = this.cars.slice(first, last);
//populate page of virtual cars
Array.prototype.splice.apply(_virtualCars, [...[first, last - first], ...loadedCars]);
this.virtualCars = _virtualCars;
this.lazyLoading = false;
}, Math.random() * 1000 + 250);
}
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="virtualCars" scrollable scrollHeight="400px" tableStyle="min-width: 50rem"
:virtualScrollerOptions="{ lazy: true, onLazyLoad: loadCarsLazy, itemSize: 46, delay: 200, showLoader: true, loading: lazyLoading, numToleratedItems: 10 }">
<Column field="id" header="Id" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
<Column field="vin" header="Vin" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="year" header="Year" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="30%" height="1rem" />
</div>
</template>
</Column>
<Column field="brand" header="Brand" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="color" header="Color" style="width: 20%">
<template #loading>
<div class="flex align-items-center" :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CarService } from '@/service/CarService';
onMounted(() => {
cars.value = Array.from({ length: 100000 }).map((_, i) => CarService.generateCar(i + 1));
});
const cars = ref();
const virtualCars = ref(Array.from({ length: 100000 }));
const lazyLoading = ref(false);
const loadLazyTimeout = ref();
const loadCarsLazy = (event) => {
!lazyLoading.value && (lazyLoading.value = true);
if (loadLazyTimeout.value) {
clearTimeout(loadLazyTimeout.value);
}
//simulate remote connection with a timeout
loadLazyTimeout.value = setTimeout(() => {
let _virtualCars = [...virtualCars.value];
let { first, last } = event;
//load data of required page
const loadedCars = cars.value.slice(first, last);
//populate page of virtual cars
Array.prototype.splice.apply(_virtualCars, [...[first, last - first], ...loadedCars]);
virtualCars.value = _virtualCars;
lazyLoading.value = false;
}, Math.random() * 1000 + 250);
};
<\/script>`
}
};
},
mounted() {
this.cars = Array.from({ length: 100000 }).map((_, i) => CarService.generateCar(i + 1));
},
methods: {
loadCarsLazy(event) {
!this.lazyLoading && (this.lazyLoading = true);
if (this.loadLazyTimeout) {
clearTimeout(this.loadLazyTimeout);
}
//simulate remote connection with a timeout
this.loadLazyTimeout = setTimeout(() => {
let _virtualCars = [...this.virtualCars];
let { first, last } = event;
//load data of required page
const loadedCars = this.cars.slice(first, last);
//populate page of virtual cars
Array.prototype.splice.apply(_virtualCars, [...[first, last - first], ...loadedCars]);
this.virtualCars = _virtualCars;
this.lazyLoading = false;
}, Math.random() * 1000 + 250);
}
}
};
</script>

View file

@ -0,0 +1,94 @@
<template>
<DocSectionText v-bind="$attrs">
<p>
Virtual Scrolling is an efficient way to render large amount data. Usage is similar to regular scrolling with the addition of <i>virtualScrollerOptions</i> property to define a fixed <i>itemSize</i>. Internally,
<NuxtLink to="/virtualscroller">VirtualScroller</NuxtLink> component is utilized so refer to the API of VirtualScroller for more information about the available options.
</p>
<p>In this example, <strong>100000</strong> preloaded records are rendered by the Table.</p>
</DocSectionText>
<div class="card">
<DataTable :value="cars" scrollable scrollHeight="400px" :virtualScrollerOptions="{ itemSize: 46 }" tableStyle="min-width: 50rem">
<Column field="id" header="Id" style="width: 20%"></Column>
<Column field="vin" header="Vin" style="width: 20%"></Column>
<Column field="year" header="Year" style="width: 20%"></Column>
<Column field="brand" header="Brand" style="width: 20%"></Column>
<Column field="color" header="Color" style="width: 20%"></Column>
</DataTable>
</div>
<DocSectionCode :code="code" :service="['CarService']" />
</template>
<script>
import { CarService } from '@/service/CarService';
export default {
data() {
return {
cars: null,
code: {
basic: `
<DataTable :value="cars" scrollable scrollHeight="400px" :virtualScrollerOptions="{ itemSize: 46 }" tableStyle="min-width: 50rem">
<Column field="id" header="Id" style="width: 20%"></Column>
<Column field="vin" header="Vin" style="width: 20%"></Column>
<Column field="year" header="Year" style="width: 20%"></Column>
<Column field="brand" header="Brand" style="width: 20%"></Column>
<Column field="color" header="Color" style="width: 20%"></Column>
</DataTable>`,
options: `
<template>
<div class="card">
<DataTable :value="cars" scrollable scrollHeight="400px" :virtualScrollerOptions="{ itemSize: 46 }" tableStyle="min-width: 50rem">
<Column field="id" header="Id" style="width: 20%"></Column>
<Column field="vin" header="Vin" style="width: 20%"></Column>
<Column field="year" header="Year" style="width: 20%"></Column>
<Column field="brand" header="Brand" style="width: 20%"></Column>
<Column field="color" header="Color" style="width: 20%"></Column>
</DataTable>
</div>
</template>
<script>
import { CarService } from '@/service/CarService';
export default {
data() {
return {
cars: null
};
},
mounted() {
this.cars = Array.from({ length: 100000 }).map((_, i) => CarService.generateCar(i + 1));
}
};
<\/script>`,
composition: `
<template>
<div class="card">
<DataTable :value="cars" scrollable scrollHeight="400px" :virtualScrollerOptions="{ itemSize: 46 }" tableStyle="min-width: 50rem">
<Column field="id" header="Id" style="width: 20%"></Column>
<Column field="vin" header="Vin" style="width: 20%"></Column>
<Column field="year" header="Year" style="width: 20%"></Column>
<Column field="brand" header="Brand" style="width: 20%"></Column>
<Column field="color" header="Color" style="width: 20%"></Column>
</DataTable>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { CarService } from '@/service/CarService';
const cars = ref();
onMounted(() => {
cars.value = Array.from({ length: 100000 }).map((_, i) => CarService.generateCar(i + 1));
});
<\/script>`
}
};
},
mounted() {
this.cars = Array.from({ length: 100000 }).map((_, i) => CarService.generateCar(i + 1));
}
};
</script>