- Home
- Forum
- Angular - EJ 2
- Angular Syncfusion Custom Validation for ejs child grid column Not Mapping Properly After Save
Angular Syncfusion Custom Validation for ejs child grid column Not Mapping Properly After Save
I am using Syncfusion EJS Grid in Angular with custom validation for add/update operations . Before clicking the Save button, validation works correctly, highlighting errors as expected. However, after clicking Save, the validation does not map properly—either it resets or does not trigger correctly on certain fields
After clicking save button and edit any row it is showing older error msg.Even after deleteing cache it is displaying the older validation msg,even after removing the line from the code.
here is the code provided ,please let us know the changes required.
HTML
<ejs-grid #VariationQSORgrid id="VariationQSORgrid" [dataSource]='VariationQSORDatasource'
[editSettings]='VariationQSOReditSettings' [toolbar]="VariationQSORtoolbar"
height="220" [allowResizing]="true" [allowSorting]="true" gridLines='Both'
[allowReordering]="true" showColumnMenu='true' [groupSettings]='groupOptions'
allowFiltering='true' [enableStickyHeader]="true" [filterSettings]='filterSettings'
[allowExcelExport]="true" [allowPdfExport]="true"
[selectionSettings]="{ type: 'Multiple' }" [showColumnChooser]="true"
(created)="onCreatedVariationQSOR()"
(headerCellInfo)="headerCellInfoVariation($event)"
(queryCellInfo)="queryCellInfoVariation($event)"
(actionBegin)="onActionBeginVariationQSOR($event)"
(actionComplete)="onActionCompleteVariationQSOR($event)"
(columnMenuOpen)="columnMenuOpen($event)"
(recordDoubleClick)="onDoubleClick($event)"
(resizeStop)="columnResize(VariationQSORgrid,gridNames.VariationQSORgrid)"
(columnMenuClick)="columnMenuClick(VariationQSORgrid,gridNames.VariationQSORgrid)">
<e-columns>
<e-column field='id' headerText='Id' width="50" [visible]="false"
[showInColumnChooser]="false" textAlign="center"></e-column>
<e-column field='commercialSuiteProjectId' headerText='CommercialSuiteProjectId'
width="50" [visible]="false" [showInColumnChooser]="false"
textAlign="center"></e-column>
<e-column field='sorReference' headerTextAlign="center"
[headerText]="l('Reference')" width='140' textAlign="left"
[allowEditing]="isAddHoc" [validationRules]="SorReferenceQSORRule" >
<ng-template #template let-record>
<span tooltip="{{record.sorReference}}"
placement="left">{{record.sorReference}}</span>
</ng-template>
</e-column>
.ts
public customQSORValidator: (args: { [key: string]: string }) => boolean = (args: { [key: string]: string }) => {
const gridName = ((args.element as any).id).split((args.element as any).name)[0];
let grid: GridComponent | undefined;
if (gridName === 'VariationQSORgrid') {
this.VariationQSORgrid.forEach((gridInstance: GridComponent) => {
grid = gridInstance;
});
}
const newValue = args.value;
const isDuplicate = (grid?.dataSource as Object[]).some(item =>
item['sorReference'] === newValue &&
(!this.isEditMode|| item['sorReference'] !== this.editingOldSorRefVal)
);
if (isDuplicate) {
return false;
}
return true;
};
SorReferenceQSORRule = { required: true, min: [this.customQSORValidator.bind(this),'Please Enter a Unique Reference Value'] };
getAllChildGridItems(voId) {
return new Promise(async (resolve, reject) => {
const processChildItems = async (result) => {
result = result && result.length > 0 ? result : [];
this.VariationQSORDatasource = result.filter(f => f.childType == (this.isAddHoc ? "addHoc" : "qsor"));
this.calculateTotalsVariationQSOR();
await this.getAllVariationSummaryItems();
if (this.isPageInitialized == false) {
this.StaticQSORArray = _.cloneDeep(this.VariationQSORDatasource);
this.isPageInitialized = true;
}
resolve(true);
};
var currentVariation = this.AllVariations.find(x => x.id == voId);
if (currentVariation && currentVariation.id) {
const currVariationVal = _.cloneDeep(currentVariation.value);
this.VariationQSORDatasource = [];
this.isAddHoc = currentVariation.isAddHocEnabled;
if (!currentVariation.isStandaloneVoData && currentVariation.isAddHocEnabled) {
var _grossMargin = currVariationVal - currentVariation.cost;
this.VariationQSORDatasource.push({
id: 0,
sorReference: "",
description: "Un Categorized",
quantity: 1,
unitSell: currVariationVal || 0,
totalSell: currVariationVal || 0,
unitCost: currentVariation.cost || 0,
totalCost: currentVariation.cost || 0,
grossMargin: _grossMargin,
grossMarginPercentage: !currVariationVal ? 0 : (_grossMargin / currVariationVal) * 100,
costCodeName: "",
variationId: this.VariationForm.get('id').value || 0,
commercialSuiteProjectId: this.projectId,
isVariationsData: true,
isAddHocData: true,
});
this.calculateTotalsVariationQSOR();
await this.getAllVariationSummaryItems();
this.StaticQSORArray = _.cloneDeep(this.VariationQSORDatasource);
resolve(true);
}
else {
this._aFPVariationSummaryServiceProxy.getAllVariationsChildItems(this.projectId, this.isAddHoc, voId ?? 0).subscribe(processChildItems);
}
}
else {
this._aFPVariationSummaryServiceProxy.getAllVariationsChildItems(this.projectId, this.isAddHoc, 0).subscribe(processChildItems);
}
});
}
async saveDropDown(e) {
await this.updateOpenEditRow();
if (!this.ValidateObject(this.VariationForm.value)) {
this.message.warn(this.l('ErrorMsg'));
this.activeStepIndex = 0;
return;
}
const voExists = this.AllVariations.some(x =>
x.id !== this.VariationForm.get('id').value && x.voNumber?.toString()?.trim() === this.VariationForm.get('voNumber').value?.toString()?.trim()
);
if (voExists) {
this.message.warn(this.l('Entered VO Number already exists for this Job'));
return;
}
this.spinnerService.show();
if (e.item.id === "save") {
this.save(true).then(() => {
this.spinnerService.hide();
this.VariationQSORgrid.first?.refresh();
});
}
else if (e.item.id === "saveAndClose") {
this.save(true).then(() => {
this.spinnerService.hide();
this.close();
});
}
}
save(showSpinner = false) {
return new Promise((resolve, reject) => {
var _data: CreateOrEditAFPVariationSummaryDto = new CreateOrEditAFPVariationSummaryDto();
_data = _.cloneDeep(this.VariationForm.value);
_data.isAddHocEnabled = this.isAddHoc;
_data.isStandaloneVoData = true;
_data.commercialSuiteProjectId = this.projectId;
_data.value = this.TotalPriceVS;
_data.cost = this.TotalCostVS;
_data.best = this.TotalPriceVS;
_data.pm = this.AllVariations.find(f => f.id == this.VariationForm.get('id').value)?.pm || 0;
const foundItem = this.CASdropdown.find(c => c.id == _data.customerAgreementStatusId);
_data.ptp = foundItem ? !_data.value ? 0 : (foundItem.percentageOfBest * _data.value) / 100 : 0;
_data.qsoRs = [];
if (this.VariationQSORDatasource && this.VariationQSORDatasource.length > 0) {
this.VariationQSORDatasource.forEach((element, index) => {
let dobj: QSORDto = new QSORDto();
Object.assign(dobj, element);
dobj.costCodeId = this.costCodes.find((costCode) => costCode.costCodeName === element.costCodeName)?.id;
_data.qsoRs[index] = dobj;
});
}
this.spinnerService.show();
this._aFPVariationSummaryServiceProxy.createOrEdit(_data).subscribe(async result => {
this.spinnerService.hide();
if (!_data.id && result && result.id > 0) {
_data.id = result.id;
this.VariationForm.controls['id'].setValue(result.id);
}
var idx = this.AllVariations.findIndex(x => x.id == result.id);
if (idx == -1 && !this.AllVariations[0].id) this.AllVariations[0].id = result.id;
idx = this.AllVariations.findIndex(x => x.id == result.id);
if (idx != -1) Object.assign(this.AllVariations[idx], result);
await this.getAllChildGridItems(result.id);
this.StaticQSORArray = _.cloneDeep(this.VariationQSORDatasource);
this.saveEmitter.emit(_data);
if (showSpinner) this.message.info(this.l('SavedSuccessfully'));
this.VariationQSORgrid.first?.refresh();
resolve(true);
},
() => {
this.spinnerService.hide();
this.message.warn(this.l('FailedToSave'));
}
);
});
}
onActionBeginVariationQSOR(args) {
if (args.requestType === 'beginEdit') {
if (!args.rowData.grossMarginPercentage) args.rowData.grossMarginPercentage = 0;
this.isEditMode = true;
this.editingOldSorRefVal = (args.rowData as any).sorReference;
}
else if (args.requestType === 'add') {
this.isEditMode = false;
this.editingOldSorRefVal = null;
}
if (args.requestType === "save") {
args.data.totalSell = args.data.unitSell * args.data.quantity;
args.data.totalCost = args.data.unitCost * args.data.quantity;
args.data.grossMargin = args.data.totalSell - args.data.totalCost;
args.data.grossMarginPercentage = (args.data.grossMargin / args.data.totalSell) * 100;
args.data.commercialSuiteProjectId = this.projectId;
const forbiddenValues = ['id', 'variationId', 'isVariationsData', 'isAddHocData', 'costCodeId'];
for (const [key, value] of Object.entries(args.data)) {
if (!forbiddenValues.includes(key) && (value == null || value === undefined || (typeof value === 'string' && value.trim().length === 0))) {
this.message.warn(this.l('Enter All Fields'));
return args.cancel = true;
}
}
}
}
async onActionCompleteVariationQSOR(args) {
var Grid: GridComponent;
this.VariationQSORgrid.forEach((gridInstance: GridComponent) => {
Grid = gridInstance;
});
console.log('request tyepe:', args.requestType);
if (args.requestType === 'beginEdit') {
args.form.querySelector('#' + Grid.element.id + this.setFocus.field).focus();
}
if (args.requestType === 'beginEdit' || args.requestType === 'add') {
var _tempData: CreateOrEditQSORDto = new CreateOrEditQSORDto();
for (var i = 0; i < args.form.elements.length; i++) {
args.form.elements[i].oninput = () => {
var x = args.form.elements;
for (var j = 0; j < x.length; j++) {
const name = x[j].nextSibling?.getAttribute('name') || "";
const value = this.convertToNumber(x[j].value);
if (name) _tempData[name] = value;
switch (name) {
case "totalSell":
_tempData.totalSell = (_tempData.unitSell || 0) * (_tempData.quantity || 0);
x[j].value = _tempData.totalSell.toLocaleString('en-GB', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
break;
case "totalCost":
_tempData.totalCost = (_tempData.unitCost || 0) * (_tempData.quantity || 0);
x[j].value = _tempData.totalCost.toLocaleString('en-GB', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
break;
case "grossMargin":
_tempData.grossMargin = (_tempData.totalSell || 0) - (_tempData.totalCost || 0);
x[j].value = _tempData.grossMargin.toLocaleString('en-GB', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
break;
case "grossMarginPercentage":
_tempData.grossMarginPercentage = ((_tempData.grossMargin / _tempData.totalSell) * 100) || 0;
x[j].value = _tempData.grossMarginPercentage.toLocaleString('en-GB', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
break;
default:
break;
}
}
}
}
}
if (args.requestType === 'save') {
if (this.VariationQSORDatasource && args.data) {
const newReference = args.data.sorReference;
const oldReference = args.previousData?.sorReference;
console.log("old,new:",newReference,oldReference);
const existingIndex = this.VariationQSORDatasource.findIndex(
(item) => item.sorReference === oldReference
);
if (existingIndex !== -1) {
//updating row
this.VariationQSORDatasource[existingIndex] = { ...args.data };
} else {
//adding new row
const alreadyExists = this.VariationQSORDatasource.some(
(item) => item.sorReference === newReference
);
if (!alreadyExists) {
this.VariationQSORDatasource.push({ ...args.data });
this.VariationQSORDatasource.refresh();
}
}
this.VariationQSORDatasource = [...this.VariationQSORDatasource];
await this.updateVariationSummaryDs();
this.calculateTotalsVariationSummary();
this.calculateTotalsVariationQSOR();
this.VariationQSORgrid.first?.endEdit();
this.VariationQSORgrid.first?.refresh();
}
}
if (["columnstate", "reorder", "filtering", "sorting", "searching"].includes(args.requestType)) {
var Grid: GridComponent;
this.VariationQSORgrid.forEach((gridInstance: GridComponent) => {
Grid = gridInstance;
});
Grid.refresh();
this._utilsServiceComponent.createOrEditUserPersistance(Grid, this.gridNames.VariationQSORgrid, this.projectId);
}
}
CustomerID column of the child grid to check for duplicate entries and enforce unique values during editing.Code Example : customFn: (args: { [key: string]: string }) => boolean = (args: { [key: string]: string; }) => { const inputValue = args['value']?.trim().toLowerCase(); // normalize input const isDuplicate = orderDatas.some( (item: any) => item.CustomerID?.trim().toLowerCase() === inputValue ); return !isDuplicate; // return true if valid (not duplicate) }; { field: 'CustomerID', headerText: 'Customer ID', width: 120, validationRules: { required: true, custom: [ this.customFn, 'Duplicate Value, please enter unique value', ], }, }, |
- You are currently using local data to validate duplicate values. Additionally, we noticed that you are using both the
actionBeginandactionCompleteevents. Could you please elaborate on the custom logic or operations you're performing within these event handlers? - Could you confirm the edit type you are using? For example, are you using
Dialog, orTemplateediting? If you're using a custom template for the form, kindly share those details as well. - Please let us know the exact Syncfusion Grid package version and Angular version you are using in your application.
- Please share a screenshot or short video demonstrating the issue to help us visualize the problem.
- Share a minimal reproducible sample or try to replicate the issue using the sample we have provided. This will help us debug the scenario efficiently
- 1 Reply
- 2 Participants
-
AF Almas Fathima
- Mar 29, 2025 07:22 AM UTC
- Apr 4, 2025 10:24 AM UTC