Customizing a TableField Component
Less than to read
This page summarizes how to provide customization on a TableField, by overriding default properties in an extension page. We will demonstrate using the MobileInternalStockChange page Override and customize the Stock TableField.
This customization is an extension of the standard mobile application, see also Extending a standard mobile application.
To begin, in the development environment, within the directory /xtrem-dev/xtrem/x3-services/application/x3-stock/lib/
, create a directory called page-extensions
if it is not present. Create a new TypeScript file within it named mobile-internal-stock-change-extension.ts
.
Now use the following examples to customize any of:
- Title and Helper text
- Sort Order (orderBy) field
- MobileCard display
- onRowClick events
- hiding a standard value
- adding a field, standard or custom
Customize the title and helper text
standard TableField | customized TableField |
---|---|
Title= Stock No helper text |
Title= Stock lines Helper text= Stock lines for the Internal stock change |
![]() |
![]() |
@ui.decorators.tableFieldOverride<MobileInternalStockChangeExtension, Stock>({
// 🟢 Customize the title and helper text
title: 'Stock lines',
helperText: 'Stock lines for the internal stock change',
})
stock: ui.fields.Table<Stock>;
Customize the sort order field
standard TableField Sorted by stockID |
customized TableField Sorted by location |
---|---|
location values: CAP18, INT008, STO50 | |
![]() |
![]() |
@ui.decorators.tableFieldOverride<MobileInternalStockChangeExtension, Stock>({
// 🟢 Customize orderBy with non-transient fields only
orderBy: { location: { code: 1 } },
})
stock: ui.fields.Table<Stock>;
Customize the mobileCard display
standard TableField display | customized TableField display | ||
---|---|---|---|
quantityInPackingUnit | product | product code | quantityInPackingUnit |
location | lot | product description | product UPC code |
sublot | status | location code | |
identifier1 | identifier2 | lot | sublot |
status | identifier1 | ||
![]() |
![]() |
@ui.decorators.tableFieldO|verride<MobileInternalStockChangeExtension, Stock>({
// 🟢 Add a customized mobileCard
mobileCard: {
title: ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, ProductSite>({
bind: 'product',
valueField: { product: { code: true } },
}),
titleRight: ui.nestedFields.numeric<MobileInternalStockChangeExtension>({
bind: 'quantityInPackingUnit',
title: 'Quantity Unit',
}),
line2: ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, ProductSite>({
bind: 'product',
valueField: { product: { localizedDescription1: true } },
}),
line2Right: ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, ProductSite>({
bind: 'product',
valueField: { product: { upc: true } },
}),
line3: ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, Location>({
bind: 'location',
valueField: 'code',
}),
line4: ui.nestedFields.text<MobileInternalStockChangeExtension>({
bind: 'lot',
}),
line4Right: ui.nestedFields.text<MobileInternalStockChangeExtension>({
bind: 'sublot',
}),
line3Right: ui.nestedFields.text<MobileInternalStockChangeExtension>({
bind: 'serialNumber',
}),
line5: ui.nestedFields.reference<MobileInternalStockChangeExtension>({
bind: 'status',
valueField: { code: true },
}),
line5Right: ui.nestedFields.text<MobileInternalStockChangeExtension>({
bind: 'identifier1',
}),
},
})
stock: ui.fields.Table<Stock>;
Customize the onRowClick event
standard TableField | custom TableField |
---|---|
Values of the stock detail display: | Values of the stock detail display: |
- product - licencePlateNumber - location - location - Lot - Sublot - lotCustomField1 - lotCustomField2 - quantityInPackingUnit - packingUnitToStockUnitConversionFactor - quantityInStockUnit - allocatedQuantity - status - identifer1 - identifier2 |
- stockId - licensePlateNumber - serialNumber |
![]() |
![]() |
@ui.decorators.tableFieldOverride<MobileInternalStockChangeExtension, Stock>({
// 🟢 Add a custom onRowClick event to display new stock details
onRowClick(recordId, rowItem) {
this.stockInfo.value = [rowItem]; // populate details list
this.gridBlock.selectedRecordId = recordId; // populate grid row block
this.detailPanelSection.isHidden = false; // show details panel
this.stockPanelSection.isHidden = true; // show stock panel
this.$.showToast('onRowClick TableField overrides', { type: 'success' });
},
})
stock: ui.fields.Table<Stock>;
@ui.decorators.detailListField<MobileInternalStockChangeExtension, Stock>({
parent() {
return this.detailsBlock;
},
node: '@sage/x3-stock-data/Stock',
isTransient: true,
isFullWidth: true,
isTitleHidden: false,
title: 'Stock information',
helperText: 'Stock information for the selected stock line',
fields: [
ui.nestedFields.text({
bind: 'stockId',
title: 'Stock ID',
isReadOnly: true,
}),
ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, LicensePlateNumber>({
bind: 'licensePlateNumber',
title: 'License plate number',
valueField: 'code',
node: '@sage/x3-stock-data/LicensePlateNumber',
isReadOnly: true,
columns: [
ui.nestedFields.text({
bind: 'code',
}),
ui.nestedFields.text({
bind: '_id',
isHidden: true,
}),
],
}),
ui.nestedFields.text({
bind: 'serialNumber',
title: 'Serial no.',
isReadOnly: true,
}),
ui.nestedFields.text({
bind: 'owner',
title: 'Owner',
isReadOnly: true,
isHidden: true,
}),
],
})
stockInfo: ui.fields.DetailList<Stock>;
Hide a standard value
standard TableField | customized TableField |
---|---|
Product code value is hidden | |
![]() |
![]() |
@ui.decorators.tableFieldOverride<MobileInternalStockChangeExtension, Stock>({
// 🟢 Be able to hide product
columnOverrides: [
ui.nestedFieldOverrides.reference({
bind: 'product',
isHidden: true,
valueField: { product: { code: true } },
isMandatory: true,
}),
],
})
stock: ui.fields.Table<Stock>;
Note: Some properties cannot be overridden with nestedFieldOverrides. They include: |
---|
- isTransient - isTransientInput - node - columns |
Add standard or custom fields to TableField
standard TableField | customized TableField |
---|---|
values added: - location type - location category |
|
![]() |
![]() |
@ui.decorators.tableFieldOverride<MobileInternalStockChangeExtension, Stock>({
// 🟢 Add new fields to TableField: Location type and location category
columns: [
ui.nestedFieldExtensions.reference<MobileInternalStockChangeExtension, Stock, Location>({
title: 'Location type',
bind: 'location',
node: '@sage/x3-stock-data/Location',
valueField: { type: true },
isReadOnly: true,
insertBefore: 'quantityInPackingUnit',
}),
ui.nestedFieldExtensions.reference<MobileInternalStockChangeExtension, Stock, Location>({
title: 'Location category',
bind: 'location',
valueField: { category: true },
insertBefore: 'quantityInPackingUnit',
insertAfter: 'locationType',
}),
],
})
stock: ui.fields.Table<Stock>;
Note:
- The TableField of the standard page is populated by a query that does not include all the fields from the Stock table.
- To add columns to the TableField that are not present in the query, a method must be added to the page extension to query the Stock table and retrieve the data for the new columns.
The following code example shows how to add columns to the TableField that are not present in the query:
import { ProductSite } from '@sage/x3-master-data-api';
import { GraphApi } from '@sage/x3-stock-api';
import { LicensePlateNumber, Location, Stock } from '@sage/x3-stock-data-api';
import { Dict } from '@sage/xtrem-shared';
import * as ui from '@sage/xtrem-ui';
import type { MobileInternalStockChange as MobileInternalStockChangePage } from '../pages/mobile-internal-stock-change';
@ui.decorators.pageExtension<MobileInternalStockChangeExtension>({
extends: '@sage/x3-stock/MobileInternalStockChange',
})
export class MobileInternalStockChangeExtension extends ui.PageExtension<MobileInternalStockChangePage, GraphApi> {
// 🟢 Override the 'Next' button.
@ui.decorators.pageActionOverride<MobileInternalStockChangeExtension>({
async onClickAfter() {
setTimeout(
async () =>
await this._prepareStockWithCustomFields(),
1000,
);
},
})
nextButton: ui.PageAction;
@ui.decorators.tableFieldOverride<MobileInternalStockChangeExtension, Stock>({
// 🟢 Customize the title and helper text
title: 'Stock lines',
helperText: 'Stock lines for the internal stock change',
// 🟢 Customize orderBy with non-transient fields only
orderBy: { location: { code: 1 } },
// 🟢 Add a customized mobileCard
mobileCard: {
title: ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, ProductSite>({
bind: 'product',
valueField: { product: { code: true } },
}),
titleRight: ui.nestedFields.numeric<MobileInternalStockChangeExtension>({
bind: 'quantityInPackingUnit',
title: 'Quantity Unit',
}),
line2: ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, ProductSite>({
bind: 'product',
valueField: { product: { localizedDescription1: true } },
}),
line2Right: ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, ProductSite>({
bind: 'product',
valueField: { product: { upc: true } },
}),
line3: ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, Location>({
bind: 'location',
valueField: 'code',
}),
line4: ui.nestedFields.text<MobileInternalStockChangeExtension>({
bind: 'lot',
}),
line4Right: ui.nestedFields.text<MobileInternalStockChangeExtension>({
bind: 'sublot',
}),
line3Right: ui.nestedFields.text<MobileInternalStockChangeExtension>({
bind: 'serialNumber',
}),
line5: ui.nestedFields.reference<MobileInternalStockChangeExtension>({
bind: 'status',
valueField: { code: true },
}),
line5Right: ui.nestedFields.text<MobileInternalStockChangeExtension>({
bind: 'identifier1',
}),
},
// 🟢 Customize event for TableField event
onRowSelected(recordId, rowItem) {
this.$.showToast('onRowSelected TableField overrides', { type: 'success' });
},
onRowSelectedAfter(recordId, rowItem) {
this.$.showToast('onRowSelected TableField overrides', { type: 'success' });
},
onRowUnselected(recordId, rowItem) {
this.$.showToast('onRowSelected TableField overrides', { type: 'success' });
},
onRowUnselectedAfter(recordId, rowItem) {
this.$.showToast('onRowSelected TableField overrides', { type: 'success' });
},
// 🟢 Add a custom onRowClick event to display new stock details
onRowClick(recordId, rowItem) {
this.stockInfo.value = [rowItem]; // populate details list
this.gridBlock.selectedRecordId = recordId; // populate grid row block
this.detailPanelSection.isHidden = false; // show details panel
this.stockPanelSection.isHidden = true; // show stock panel
this.$.showToast('onRowClick TableField overrides', { type: 'success' });
},
// 🟢 Add a customized dropdownActions
dropdownActions: [
{
icon: 'bin',
title: 'Delete',
isDestructive: true,
async onClick(recordId) {
const row = this.stock.getRecordValue(recordId);
if (row) {
await this.stock.removeRecord(recordId);
}
},
},
],
// 🟢 Make quantityInPackingUnit visible and hide product
columnOverrides: [
ui.nestedFieldOverrides.numeric({
bind: 'quantityInPackingUnit',
isHidden: false,
}),
ui.nestedFieldOverrides.reference({
bind: 'product',
isHidden: true,
valueField: { product: { code: true } },
isMandatory: true,
}),
],
// 🟢 Add new fields to TableField: Location type and location category
columns: [
ui.nestedFieldExtensions.reference<MobileInternalStockChangeExtension, Stock, Location>({
title: 'Location type',
bind: 'location',
node: '@sage/x3-stock-data/Location',
valueField: { type: true },
isReadOnly: true,
insertBefore: 'quantityInPackingUnit',
}),
ui.nestedFieldExtensions.reference<MobileInternalStockChangeExtension, Stock, Location>({
title: 'Location category',
bind: 'location',
valueField: { category: true },
insertBefore: 'quantityInPackingUnit',
insertAfter: 'locationType',
}),
],
})
stock: ui.fields.Table<Stock>;
@ui.decorators.detailListField<MobileInternalStockChangeExtension, Stock>({
parent() {
return this.detailsBlock;
},
node: '@sage/x3-stock-data/Stock',
isTransient: true,
isFullWidth: true,
isTitleHidden: false,
title: 'Stock information',
helperText: 'Stock information for the selected stock line',
fields: [
ui.nestedFields.text({
bind: 'stockId',
title: 'Stock ID',
isReadOnly: true,
}),
ui.nestedFields.reference<MobileInternalStockChangeExtension, Stock, LicensePlateNumber>({
bind: 'licensePlateNumber',
title: 'License plate number',
valueField: 'code',
node: '@sage/x3-stock-data/LicensePlateNumber',
isReadOnly: true,
columns: [
ui.nestedFields.text({
bind: 'code',
}),
ui.nestedFields.text({
bind: '_id',
isHidden: true,
}),
],
}),
ui.nestedFields.text({
bind: 'serialNumber',
title: 'Serial no.',
isReadOnly: true,
}),
ui.nestedFields.text({
bind: 'owner',
title: 'Owner',
isReadOnly: true,
isHidden: true,
}),
],
})
stockInfo: ui.fields.DetailList<Stock>;
public async _prepareStockWithCustomFields() {
const stockIds = this.stock?.value?.map(stockLine => stockLine.stockId) ?? [];
const response = await this.$.graph
.node('@sage/x3-stock-data/Stock')
.query(
ui.queryUtils.edgesSelector(
{
_id: true,
product: {
product: {
code: true,
localizedDescription1: true,
upc: true,
_id: true,
},
_id: true,
},
location: {
type: true,
category: true,
_id: true,
},
stockId: true,
},
{
filter: {
stockId: { _in: stockIds },
},
first: 1000,
},
),
)
.execute();
this.stock.value = this.stock.value.map((stockLine) => {
const stockItem = response.edges?.find(edge => String(edge.node.stockId) === String(stockLine.stockId));
if (!stockItem) {
return stockLine;
}
return {
...stockLine,
product: {
product:
{
...stockLine.product?.product,
localizedDescription1: stockItem.node.product?.product?.localizedDescription1,
upc: stockItem.node.product?.product?.upc,
},
},
location: {
...stockLine.location,
type: stockItem.node.location?.type,
category: stockItem.node.location?.category,
},
};
});
};
}