Data grid input validation in IBM DOC 4.0

Cedric Villeneuve
Decision Optimization Center
5 min readJan 20, 2021

Introduction

Use case description

In this example, we will see how to add custom validation in a cell editor, like checking the range of a number, or the format of an input.

Solution overview

To achieve this, we will implement a custom controller for the data-grid, a new mechanism introduced in IBM DOC 4.0.0-FP4. This custom controller will be used to replace the standard cell editor with a new component, that includes validation and feedback.

The solution will consist of the following elements:

  • a validation service to centralize validation rules
  • a custom table controller to customize the column configurations
  • a custom input editor component to apply validation rules and display feedback

Implementation

Validation service

In this article, we will assume that we have centralized the validation business logic into a service called ValidationService. The service’s declaration looks like the following:

@Injectable({providedIn: 'root'}) 
export class ValidationService {
public hasRulesFor(geneEntity: string, geneEntityField: string): boolean {
// ...
return false;
}

/**
* return an array with one string for each validation error.
* Empty array if the input is valid.
*/

public validate(entity: string, field: string, value: any): string[] {
let errors = [];
//...
return errors;
}
}

The rules are centralized, and based on the entity and field to check, so there’s only one custom controller and one editor component to be implemented for all the possible data types in the application.

Custom table controller

Controller definition and registration

The implementation of a custom controller is described in the IBM DOC 4.0.0-fp4 documentation.

The controller class declaration is as follows:

export class InputValidationTableController extends       
GeneTableController<GeneEntity> {
}

All methods of GeneTableController are optional, therefore an empty controller can be declared and registered.

The controller is registered in the module’s initialization code:

customWidgetFactory.registerCustomWidgetController(
GeneTableComponent.MANIFEST,
'InputValidationTableController',
(widget: GeneTableComponent<any>) =>
new InputValidationTableController(widget));

With these steps you should be able to select the new controller from the data grid widget configurator:

custom controller
The custom controller can now be selected in the widget’s configuration

Table controller methods

Implementing a custom table controller allows us to do several things:

  • customize the configuration for one column, which will see in details in the next paragraph
  • implement additional columns, that may be required regardless of the user’s configuration, or that could contain custom rendering or computed data
  • customize the grid options
  • change the way the data are loaded or processed before populating the grid

To implement a custom editor with validation, we will override the customizeColumn method to specify which editor component should be used.

Column customization

The code for the custom controller is quite short:

  • the constructor allows injection of the validation service
  • the customizeColumn method is declared to modify the cellEditor
export class InputValidationTableController extends 
GeneTableController<GeneEntity> {
constructor(private validationService: ValidationService,
widget: GeneTableComponent<GeneEntity>) {
super(widget);
}

customizeColumn = (column: ColDef,
editionMode: GeneEditionMode,
field: GeneField) => {
const entity = this.widget?._widgetConfiguration?.dataType;
const field = field?.name;
if (this.validationService.hasRulesFor(entity, field)) {
column.cellEditor = undefined;
column.cellEditorFramework = ValidationCellEditorComponent;
column.cellEditorParams.geneEntityField = entity;
column.cellEditorParams.geneEntity = field;
}
}
}

First, we retrieve the entity from the configuration of the widget attached to the current controller and the field name from the method parameters.

This information is used to determine if there are validation rules defined for this entity and field (validationService call). This allows using the standard editors for fields that don’t have validation. For example, a foreign key selector is already handled by the platform.

The column definition is then updated accordingly:

  • unset the cellEditor property that is used by the standard controller
  • define the cellEditorFramework property to use the custom ValidationCellEditorComponent (described in the next part)
  • the geneEntity and geneEntityField information is also added to the cellEditorParams property, so they can easily be accessed by the cell editor component. Note that cellEditorParams already contains information required by the platform and should not be overwritten completely.

Custom cell editor component

HTML and CSS

The component is a simple input. A container and icon have been added to make the display of errors more explicit. A checkValue method is called on keyup events to update the styles and error description.

<div class="gene-cell-editor" tabindex="0">     
<input #input
[(ngModel)]="value"
(keyup)="checkValue()"
[className]="hasError ? 'invalid input': 'input'" >
<div *ngIf="hasError" class="icon-container">
<clr-icon shape="exclamation-circle"
size="20"
class="error-icon"
[title]="errorDescription">
</clr-icon>
</div>
</div>

The styles are defined to let the input look as close as possible to the standard input, and show validation errors very clearly (light red background, dark red border and icon). The icon-container div is only present to center the icon vertically.

.input {   
border:solid 1px #009bed;
padding-left:10px;
}
.icon-container {
position: absolute;
top:0;
right:0;
bottom:0;
width:24px;
display:flex;
align-items: center;
}
.error-icon {
color: #9f0f0f;
}
.invalid {
background:#FFE8E8;
color: #9f0f0f;
border-color: #9f0f0f;
}

Component code

The code is pretty straightforward:

  • the constructor injects the validation service
  • entity, field and value are initialized in the agInit method
  • value is bound to the input field by [(ngModel)]=”value” in the HTML part
  • the checkValue method calls the validation service and updates hasError and errorDescription
  • isCancelAfterEnd() is overridden to return false if errors are present. This means that if an error is detected, the value change won’t be committed. This will prevent the application from saving invalid data.
@Component({     
selector: 'app-validation-cell-editor',
templateUrl: './validation-cell-editor.component.html',
styleUrls: ['./validation-cell-editor.component.scss']
})
export class ValidationCellEditorComponent implements
ICellEditorAngularComp, AfterViewInit {
public value: any;
private entity: string;
private field: string;
hasError: boolean = false;
errorDescription: string = null;
@ViewChild('input', {read: ViewContainerRef}) public input; constructor(private validationService: ValidationService) {
}
agInit(params: ICellEditorParams): void {
this.entity = params.colDef?.cellEditorParams?.geneEntity;
this.field = params.colDef?.cellEditorParams?.geneEntityField;
this.value = this.params.value;
}
getValue(): any {
return this.value;
}
isCancelBeforeStart(): boolean {
return false;
}
isCancelAfterEnd(): boolean {
return this.hasError;
}
ngAfterViewInit() {
setTimeout(() => {
this.input.element.nativeElement.focus();
});
}
checkValue() {
const errors = this.validationService.validate(this.entity,
this.field, this.value);
this.hasError = errors.length > 0;
this.errorDescription = errors.join("\n");
}
}

Conclusion

Final result

This is how the editor looks when a validation error is detected while editing the value. It uses the browser’s native tooltip, which could be improved:

how the editor looks when a validation error is detected

As soon as the value is correct, the style is back to the default style :

how the editor looks when is validated

Going further

Validation using several fields of the current row is possible: the params object received by the editor component in the agInit method has a data field that contains all properties of the current row. This can be used to implement rules such as Field A > Field B. Or Field A is required if Field B is undefined.

In this example, the custom editor was used for validation, but it can also be used for any kind of custom value editor (e.g. text field with auto-completion, list selector).

A custom renderer can be defined and used in a very similar way, that will display errors on existing data, or on currently edited data if isCancelAfterEnd is modified to return true (allow committing invalid data).

IBM® Decision Optimization Center provides a configurable platform for building, deploying, and monitoring decision support applications for business users.

Learn more about IBM Decision Optimization Center

--

--