Prevent User from Adding Duplicate Items in Grid

I have a grid where the user can add items to the grid. When they are adding an item I need to be able to check that they have not already added the item to the list. For instance, in the demo below I have added a tool to the grid. I would not be able to add that same tool to the grid a second time.

@Html.EJS().Grid("StockGrid").DataSource(DataManager => { DataManager.Json(@Model.ContainerTransactions.ToArray()).InsertUrl(@Url.Action("AddContainerTransaction", "Containers")).UpdateUrl(@Url.Action("UpdateContainerTransaction", "Containers")).RemoveUrl(@Url.Action("DeleteContainerTransaction", "Containers")).Adaptor("RemoteSaveAdaptor"); }).AllowFiltering(true).Load("onLoad").ToolbarClick("toolbarClick").EnableAdaptiveUI(false).RowRenderingMode(Syncfusion.EJ2.Grids.RowRenderingDirection.Horizontal).Width("auto").Columns(col =>

           {

               col.Field("TransactionNo").Visible(false).Add();

                col.Field("InventoryID").HeaderText("Item").IsPrimaryKey(true).Width("250").Visible(false).EditType("dropdownedit").Edit(new { create = "create", read = "read", destroy = "destroy", write = "write" }).ValidationRules(new { required = true }).Add();

               col.Field("IsRestock").HeaderText("Restock").Type("boolean").EditType("booleanedit").DisplayAsCheckBox(true).DefaultValue(true).Width("110").Add();

               col.Field("ExpectedStock").HeaderText("Expected Stock").ValueAccessor(stockAccessor).Type("number").Filter(new { type = "CheckBox", itemTemplate = "${ stockTemp(data) }" }).AllowEditing(false).Width("160").Add();

               col.Field("ActualStock").HeaderText("Actual Stock").Type("number").EditType("numericedit").Edit(new

               {

                   @params = new Syncfusion.EJ2.Inputs.NumericTextBox()

                   {


                       ValidateDecimalOnType = true,

                       Decimals = 0,

                       Format = "N",

                       Min = 0,

                       Change = "ActualStockChange"

                   }

               }).Width("140").Add();

               col.Field("Quantity").HeaderText("Stock Added").Type("number").EditType("numericedit").Edit(new

               {

                   @params = new Syncfusion.EJ2.Inputs.NumericTextBox()

                   {


                       ValidateDecimalOnType = true,

                       Decimals = 0,

                       Format = "N",

                       Min = 0,

                       Change ="StockAddedChange"

                   }

               }).ValidationRules(new { required = true, number = true, min = 1 }).Width("140").Add();


               col.Field("IsInventory").Visible(false).Add();

               col.Field("StockResult").HeaderText("Stock Result").ValueAccessor(resultAccessor).Type("number").AllowEditing(false).Width("140").Add();

               col.Field("MaximumStock").HeaderText("Maximum Stock").ValueAccessor(stockAccessor).Type("number").Visible(false).AllowEditing(false).Width("140").Add();

               col.Field("ItemDetails.IsMinorMaterial").HeaderText("Minor Material").ValueAccessor(minorMaterialAccessor).Filter(new { type = "CheckBox", itemTemplate = "${ materialTemp(data) }" }).AllowEditing(false).Width("160").Add();


           }).ActionBegin("actionBegin").ActionComplete("actionComplete")


<script>

  var elem;

                var inventoryObj;

                var inventoryData = @Html.Raw(Json.Encode(@Model.Inventory.ToArray()));

                var inventoryStockData = @Html.Raw(Json.Encode(@Model.InventoryStock.ToArray()));


                function create(args) {


                    elem = document.createElement('input');

                    return elem;

                }


                function destroy() {

                    inventoryObj.destroy();

                }


                function read(args) {

                    return inventoryObj.value;

                }


                function write(args) {

                    inventoryObj = new ej.dropdowns.DropDownList({

                        dataSource: inventoryData,

                        placeholder: args.column.headerText,

                        value: args.rowData[args.column.field],

                        floatLabelType: "Always",

                        fields: { text: "CatalogDescription", value: "InventoryID" },

                        change: function() {

                            inventoryChange(inventoryObj.value);

                        },

                        allowFiltering: true,

                        //bind the filtering event handler

                        filtering: (e) => {

                            // load overall data when search key empty.

                            if (e.text === '') {

                                e.updateData(inventoryData);

                            } else {

                                var dropdown_query = new ej.data.Query();

                                // frame the query based on search string with filter type.

                                dropdown_query.where(new ej.data.Predicate('CatalogDescription', 'contains', e.text, true).or('InventoryID', 'contains', e.text, true));

                                e.updateData(inventoryData, dropdown_query);

                            }

                        }


                    });


                    inventoryObj.appendTo(args.element);

                }



                function inventoryChange(args) {

                    var stock = 0;

                    var minorMaterial = false;

                    var maxStock = 0;

                    for (var i = 0; i < inventoryData.length; i++) {

                        if (args === inventoryData[i].InventoryID) {

                            minorMaterial = inventoryData[i].IsMinorMaterial;


                            for (var s = 0; s < inventoryStockData.length; s++) {

                                if (inventoryData[i].InventoryID === inventoryStockData[s].InventoryID) {

                                    stock = inventoryStockData[s].CurrentStock;

                                    maxStock = inventoryStockData[s].MaximumStock;

                                }

                            }


                        }

                    }

                    if (minorMaterial === false) {

                        document.getElementsByClassName('e-gridform')[0].ej2_instances[0].addRules('ActualStock', { required: true, number: true, min: 0 });


                    }


                    document.getElementById('StockGridExpectedStock').ej2_instances[0].value = stock;

                    document.getElementById('StockGridMaximumStock').ej2_instances[0].value = maxStock;

                }



</script>


Attachment: DuplicateItems_e08d12f3.zip

6 Replies

RR Rajapandi Ravi Syncfusion Team September 16, 2021 01:46 PM UTC

Hi Danyelle, 

Thanks for the update 

Based on your query we suspect that you need to prevent the duplicate record when inserting a new record. You can achieve your requirement by using custom validation.   
  
For your reference in this custom validation function, we checked whether the new primary key is already present in the grid dataSource or not. Based on the result we return the Boolean value. Please refer to the below code example and sample for more information.   

Likewise, you can achieve your requirement as per your needs. 


<script> 
    function complete(args) {//actionComplete event of Grid 
        if ((args.requestType === 'beginEdit' || args.requestType === 'add')) { 
            // add the custom validation rule to the primary key field 
            args.form.ej2_instances[0].addRules('OrderID', { required: true, maxLength: [customFn, "duplicate value"] }); 
        } 
 
    } 
    function customFn(args) { //custom validation function 
        var grid = document.getElementsByClassName('e-grid')[0].ej2_instances[0]; 
        var newPrimarykeyData = parseInt(args.value); 
        var primaryKey = grid.getPrimaryKeyFieldNames()[0]; 
        var records = grid.dataSource.dataSource.json; 
        // check whether the added/edited value is already present in the datasource or not  
        // likewise, you can check your added/edited value 
        var x = new ej.data.DataManager(records).executeLocal(new ej.data.Query().where(primaryKey, "equal", newPrimarykeyData)); 
        return x.length > 0 ? false : true; // return the Boolean value  
 
    } 
</script> 




Screenshot:  

 


Regards, 
Rajapandi R 



DA Danyelle September 17, 2021 04:01 PM UTC

This did not do anything. I got the validation to work by setting the custom function in the validation part of the field in the grid. See the Below code. However, I can not set the validation message this way. How can I modify the validation message?


Code:

@Html.EJS().Grid("ContainerGrid").DataSource(DataManager => { DataManager.Json(@Model.ContainerTransactions.ToArray()).InsertUrl(@Url.Action("AddContainerTransaction", "Containers")).UpdateUrl(@Url.Action("UpdateContainerTransaction", "Containers")).RemoveUrl(@Url.Action("DeleteContainerTransaction", "Containers")).Adaptor("RemoteSaveAdaptor"); }).AllowFiltering(true).AllowSorting(true).ToolbarClick("toolbarClick").Load("onLoad").EnableAdaptiveUI(false).RowRenderingMode(Syncfusion.EJ2.Grids.RowRenderingDirection.Horizontal).Width("auto").Columns(col =>

       {

           col.Field("TransactionNo").Visible(false).Add();

           col.Field("InventoryID").HeaderText("Item").IsPrimaryKey(true).Width("60").Visible(false).EditType("dropdownedit").Edit(new { create = "create", read = "read", destroy = "destroy", write = "write" }).ValidationRules(new { required = true, maxLength =duplicateFn}).Add();

           col.Field("ContainerID").ValueAccessor(containerAccessor).Visible(false).Add();


}).ActionBegin("actionBegin")


<script>

  function duplicateFn(args) { //custom validation function

                var grid = document.getElementsByClassName('e-grid')[0].ej2_instances[0];

                var newPrimarykeyData = parseInt(args.value);

                var primaryKey = grid.getPrimaryKeyFieldNames()[0];

                var records = grid.dataSource.dataSource.json;

                // check whether the added/edited value is already present in the datasource or not

                // likewise, you can check your added/edited value

                var x = new ej.data.DataManager(records).executeLocal(new ej.data.Query().where(primaryKey, "equal", newPrimarykeyData));

                return x.length > 0 ? false : true; // return the Boolean value


            }

</script>

Result:




RR Rajapandi Ravi Syncfusion Team September 20, 2021 01:40 PM UTC

Hi Danyelle, 

Thanks for the update 

We have checked your shared information and we found that you are not binding the actionComplete event in your application. By default, the actionComplete event triggers while performing the Grid actions such as sorting, filtering, paging, grouping etc. are completed. Actionbegin event triggers while performing the Grid actions such as sorting, filtering, paging, grouping etc., starts. 

While performing the save action for added records, the actionComplete event gets triggered with the form arguments. Since we need to perform the validation while saving the added record, we need to add the validation rules to the Form element by passing the columnName and custom rules. Please refer the below code example and sample for more information. 

 
  @Html.EJS().Grid("Grid").DataSource(dataManager =>{dataManager.Json(ViewBag.dataSource).InsertUrl("/Home/Insert").RemoveUrl("/Home/Delete").UpdateUrl("/Home/Update").Adaptor("RemoteSaveAdaptor"); 
}).EditSettings(e => { e.AllowAdding(true).AllowEditing(true).AllowDeleting(true); }).Columns(col => 
{ 
    col.Field("OrderID").HeaderText("Order ID").IsPrimaryKey(true).Width("30%").Add(); 
    col.Field("EmployeeID").HeaderText("Employee ID").Width("150").Add(); 
    col.Field("CustomerID").HeaderText("CustomerID").Width("70").Add(); 
 
}).AllowPaging().ActionComplete("complete").AllowSorting(true).Toolbar(new List<string>() { "Add", "Edit", "Delete", "Update", "Cancel" }).Render() 
 
<script> 
    function complete(args) { //actionComplete event of Grid 
        if ((args.requestType === 'beginEdit' || args.requestType === 'add')) { 
            // add the custom validation rule to the primary key field 
            args.form.ej2_instances[0].addRules('OrderID', { required: true, maxLength: [customFn, "duplicate value"] }); 
                                                                        //column name           //custom rules                                 //here we can set the validation message as we want 
        } 
    } 
   function customFn(args) { 
        var grid = document.getElementsByClassName('e-grid')[0].ej2_instances[0]; 
        var newPrimarykeyData = parseInt(args.value); 
        var primaryKey = grid.getPrimaryKeyFieldNames()[0]; 
        var records = grid.dataSource.dataSource.json; 
        // check whether the added/edited value is already present in the datasource or not  
        // likewise you can check your added/edited value 
        var x = new ej.data.DataManager(records).executeLocal(new ej.data.Query().where(primaryKey, "equal", newPrimarykeyData)); 
        return x.length > 0 ? false : true; // return the Boolean value  
 
    } 
</script> 


So, we suggest you use the above way to perform the validation and display the error message as you want.  


Screenshot: 

 

If you still face the problem, please try to reproduce the problem with our above attached sample or share any issue reproducible sample that will be helpful for us to validate the problem. 

Regards, 
Rajapandi R 



DA Danyelle September 20, 2021 03:44 PM UTC

I am binding the action complete event. I just did not include it in that code snippet. This is my full code:

  @Html.EJS().Grid("ContainersGrid").DataSource(DataManager => { DataManager.Json(@Model.ContainerTransactions.ToArray()).InsertUrl(@Url.Action("AddContainerTransaction", "Containers")).UpdateUrl(@Url.Action("UpdateContainerTransaction", "Containers")).RemoveUrl(@Url.Action("DeleteContainerTransaction", "Containers")).Adaptor("RemoteSaveAdaptor"); }).AllowFiltering(true).AllowSorting(true).ToolbarClick("toolbarClick").Load("onLoad").EnableAdaptiveUI(false).RowRenderingMode(Syncfusion.EJ2.Grids.RowRenderingDirection.Horizontal).Width("auto").Columns(col =>

       {

           col.Field("TransactionNo").Visible(false).Add();

           col.Field("InventoryID").HeaderText("Item").IsPrimaryKey(true).Width("60").Visible(false).EditType("dropdownedit").Edit(new { create = "create", read = "read", destroy = "destroy", write = "write" }).ValidationRules(new { required = true, maxLength = duplicateFn }).Add();

           col.Field("ContainerID").ValueAccessor(containerAccessor).Visible(false).Add();

           col.Field("CreatedDate").Visible(false).Add();

           col.Field("ItemDetails.CatalogDescription").HeaderText("Item").ValueAccessor(descriptionAccessor).Filter(new { type = "CheckBox", itemTemplate = "${ descriptionTemp(data) }" }).Width("250").ClipMode(Syncfusion.EJ2.Grids.ClipMode.EllipsisWithTooltip).Add();

           col.Field("Quantity").HeaderText("Quantity").Type("number").EditType("numericedit").Edit(new

           {

               @params = new Syncfusion.EJ2.Inputs.NumericTextBox()

               {


                   ValidateDecimalOnType = true,

                   Decimals = 0,

                   Format = "N",

                   Min = 0

               }

           }).ValidationRules(new { required = true, number = true, min = 1 }).Width("120").Add();

           col.Field("IsRestock").HeaderText("Restock").EditType("booleanedit").DisplayAsCheckBox(true).Type("boolean").Width("120").Add();

           col.Field("Notes").HeaderText("Notes").Width("150").ClipMode(Syncfusion.EJ2.Grids.ClipMode.EllipsisWithTooltip).ValidationRules(new { maxLength = 100 }).Add();


       }).ActionBegin("actionBegin").ActionComplete("actionComplete").AllowPaging(true).PageSettings(page => page.PageCount(6).PageSizes(true)).EditSettings(edit => { edit.AllowAdding(true).AllowEditing(true).AllowDeleting(true).Mode(Syncfusion.EJ2.Grids.EditMode.Dialog); }).FilterSettings(filter => { filter.Type(Syncfusion.EJ2.Grids.FilterType.Excel); }).SortSettings(sort => sort.Columns(sortColumns)).Toolbar(toolbarItems).Render()


Snippet


function actionBegin(args) {


if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {

for (var i = 0; i < this.columns.length; i++) {

if (this.columns[i].field === "ItemDetails.CatalogDescription") {

this.columns[i].visible = false;

}

if (this.columns[i].field === "InventoryID") {

this.columns[i].visible = true;

}

}



}


if (args.requestType === 'save') {

args.data.ContainerID = containerId;

}


if (args.requestType === 'delete') {

// Condition executes on performing delete action

this.query = new ej.data.Query().addParams('TransactionNo', args.data[0]["TransactionNo"]);

}

}


function actionComplete(args) {

gridObj = document.getElementById("ContainersGrid").ej2_instances[0];


if ((args.requestType === 'beginEdit' || args.requestType === 'add')) {



var dialog = args.dialog;

// change the header of the dialog

var dialogHeaderContent = args.requestType === 'beginEdit' ? 'Edit Container Item' : 'Add Container Item';

if (gridObj.enableAdaptiveUI && grid.rowRenderingMode === "Vertical") {

var headerEle = dialog.header.querySelector('.e-dlg-custom-header');

headerEle.innerText = dialogHeaderContent;

} else {

dialog.header = dialogHeaderContent;

}



var buttons = dialog.buttons;

buttons.push(buttons.splice(0, 1)[0]);

dialog.refresh();




}



for (var i = 0; i < this.columns.length; i++) {

if (this.columns[i].field === "ItemDetails.CatalogDescription") {

this.columns[i].visible = true;

}

if (this.columns[i].field === "InventoryID") {

this.columns[i].visible = false;

}

}



if (args.action === 'add') {

document.getElementById("success").textContent = "The item was added successfully.";

document.getElementById("SuccessAlert").style.display = "block";

gridObj.refresh();

setTimeout(function() { hideAlert(); }, 4000);

}

if (args.action === 'edit') {

document.getElementById("success").textContent = "The item was updated successfully.";

document.getElementById("SuccessAlert").style.display = "block";

setTimeout(function() { hideAlert(); }, 4000);

}

if (args.requestType === 'delete') {

document.getElementById("success").textContent = "The item has been deleted successfully.";

document.getElementById("SuccessAlert").style.display = "block";

setTimeout(function() { hideAlert(); }, 4000);

}


}


function duplicateFn(args) { //custom validation function

var grid = document.getElementsByClassName('e-grid')[0].ej2_instances[0];

var newPrimarykeyData = parseInt(args.value);

var primaryKey = grid.getPrimaryKeyFieldNames()[0];

var records = grid.dataSource.dataSource.json;

// check whether the added/edited value is already present in the datasource or not

// likewise, you can check your added/edited value

var x = new ej.data.DataManager(records).executeLocal(new ej.data.Query().where(primaryKey, "equal", newPrimarykeyData));

return x.length > 0 ? false : true; // return the Boolean value


}




DA Danyelle September 21, 2021 01:43 PM UTC

The issue was with the logic determining if there was a duplicate value or not. The primaryKey value was only returning the name of the primary key not an actual value. Therefore the primaryKey and the newPrimarykeyData never matched and x always returned zero. Below is the original function followed by the changes I made to get it to work.

Old Function

function duplicateFn(args) { //custom validation function

var grid = document.getElementsByClassName('e-grid')[0].ej2_instances[0];

var newPrimarykeyData = parseInt(args.value);

var primaryKey = grid.getPrimaryKeyFieldNames()[0]; //Only gives name, not value

var records = grid.dataSource.dataSource.json;

// check whether the added/edited value is already present in the datasource or not

// likewise, you can check your added/edited value

var x = new ej.data.DataManager(records).executeLocal(new ej.data.Query().where(primaryKey, "equal", newPrimarykeyData));

return x.length > 0 ? false : true; // return the Boolean value


}


New Function

function duplicateFn(args) { //custom validation function

var grid = document.getElementsByClassName('e-grid')[0].ej2_instances[0];

var newPrimarykeyData = parseInt(args.value);

var records = grid.dataSource.dataSource.json;

var x = 0;

for (var i = 0; i < records.length; i++) {

if (parseInt(records[i].InventoryID) === newPrimarykeyData) {

x = 1;

}

}


return x > 0 ? false : true; // return the Boolean value




}



RR Rajapandi Ravi Syncfusion Team September 21, 2021 02:22 PM UTC

Hi Danyelle, 

Thanks for the update 

We have checked your shared information and we could see that you are maintaining a Dialog edit mode and using cell edit template in the ‘InventoryID’ column. So based on your application scenario we have prepared a sample and tried to reproduce the problem, but it was unsuccessful. Please refer the below sample for more information. 


From validating your shared information, we found that in actionComplete event of Grid you are not adding the validation rules to the form element. Incase if you have missed to share the code part. Please share the code part, we would like to see how you are setting the validation rules to the form element in the actionComplete event.  

From validating your shared code, we could see that you are setting a validationRules in Grid column level. Please refer the below code example 



col.Field("TransactionNo").Visible(false).Add(); 
col.Field("InventoryID").HeaderText("Item").IsPrimaryKey(true).Width("60").Visible(false).EditType("dropdownedit").Edit(new { create = "create", read = "read", destroy = "destroy", write = "write" }).ValidationRules(new { required = true, maxLength = duplicateFn }).Add(); 


Since we suggest you define the validation rules in the actionComplete event. For checking purpose, please remove the validationRules property in the Grid column definition and check whether the validationRules is working fine or not. 

Regards, 
Rajapandi R 


Loader.
Up arrow icon