DEV Community

Explorer
Explorer

Posted on

Preventing Duplicate Column Values in Joget Form Grids via BeanShell

When users submit tabular data inside a Joget DX Form Grid (such as multi-line expense claims, asset transfers, or approver lists), users frequently enter duplicate items by accident.

While standard form validators check whether individual cells are empty or match a format, they cannot compare cells across different grid rows.

In this guide, we'll write a custom BeanShell Form Validator script attached to a Form Grid that detects duplicate column values across grid rows, identifies the exact row numbers where duplicates occur, and halts form submission with a clear error message.


How It Works

  1. Row Iteration: The validator receives the submitted grid rows as a FormRowSet collection (rows).
  2. Frequency Map Tracking: Using a HashMap<String, List<Integer>>, the script tracks each value along with a list of row indices where that value appears.
  3. Descriptive Error Reporting: If any value occurs more than once, an error message specifies the exact duplicate value and row locations (e.g., Duplicate Serial Number 'SN-9021' found at Rows: 2, 5).

The BeanShell Form Validator Script

In Joget Form Builder, edit your Form Grid element, set Validator to BeanShell Validator, and paste the script below:

import org.joget.apps.form.model.Element;
import org.joget.apps.form.model.FormData;
import org.joget.apps.form.model.FormRow;
import org.joget.apps.form.service.FormUtil;
import org.joget.commons.util.LogUtil;
import java.util.*;

public boolean validateGridDuplicates(Element element, FormData formData, FormRowSet rows) {
    String elementId = FormUtil.getElementParameterName(element);

    if (rows == null || rows.isEmpty()) {
        return true; // No rows to validate
    }

    // Name of the grid column property to check for duplicates
    String targetColumnProperty = "serial_number";

    // Map: Key = Column Value, Value = List of 1-based Row Numbers
    Map valueOccurrencesMap = new HashMap();
    int currentRowNumber = 0;

    for (Object oRow : rows) {
        FormRow row = (FormRow) oRow;
        currentRowNumber++;

        String cellValue = row.getProperty(targetColumnProperty);
        if (cellValue != null && !cellValue.trim().isEmpty()) {
            cellValue = cellValue.trim();

            if (!valueOccurrencesMap.containsKey(cellValue)) {
                valueOccurrencesMap.put(cellValue, new ArrayList());
            }
            List rowList = (List) valueOccurrencesMap.get(cellValue);
            rowList.add(Integer.valueOf(currentRowNumber));
        }
    }

    // Identify duplicates (values appearing in more than 1 row)
    boolean hasDuplicates = false;
    for (Object oEntry : valueOccurrencesMap.entrySet()) {
        Map.Entry entry = (Map.Entry) oEntry;
        String val = (String) entry.getKey();
        List rowIndices = (List) entry.getValue();

        if (rowIndices.size() > 1) {
            hasDuplicates = true;

            // Format row indices (e.g., "2, 5")
            StringBuilder rowsStr = new StringBuilder();
            for (int i = 0; i < rowIndices.size(); i++) {
                rowsStr.append(rowIndices.get(i).toString());
                if (i < rowIndices.size() - 1) {
                    rowsStr.append(", ");
                }
            }

            String errorMsg = "Duplicate '" + val + "' found in grid at Row(s): " + rowsStr.toString() + ". Duplicate entries are not allowed.";
            formData.addFormError(elementId, errorMsg);
            LogUtil.warn("Grid Validator", errorMsg);
        }
    }

    return !hasDuplicates;
}

// Execute validation against submitted rows
return validateGridDuplicates(element, formData, rows);
Enter fullscreen mode Exit fullscreen mode

Key Benefits

  • Clear Feedback: Users immediately know which rows contain duplicate data without having to scan through 30+ grid rows manually.
  • Data Integrity: Prevents duplicate primary entries or conflicting allocations from being stored in relational database tables.

Top comments (0)