Angular Hierarchical Grid の編集と検証
Hierarchical Grid の編集は、セル/行の編集時のユーザー入力の組み込み検証メカニズムを公開します。これは Angular Form 検証 機能を拡張し、既知の機能と簡単に統合できるようにします。エディターの状態が変更されると、視覚的なインジケーターが編集されたセルに適用されます。
構成
テンプレート駆動で構成する
Angular Forms 検証ディレクティブは、IgxColumn
で直接動作するよう拡張されています。同じ検証が igx-column
で宣言的に設定される属性として利用できます。以下の検証は追加設定なしでサポートされます。
required
min
max
email
minlength
maxlength
pattern
列入力が設定され、値がメールとして書式設定されることを検証するには、関連するディレクティブを使用できます。
<igx-column [field ]="email" [header ]="User E-mail" required email > </igx-column >
html
以下のサンプルは、Hierarchical Grid に組み込み済みの required
、email
および min
検証ディレクティブを使用する方法を示しています。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { HierarchicalGridValidatorServiceComponent } from "./hierarchical-grid/hierarchical-grid-validator-service/hierarchical-grid-validator-service.component" ;
import {
IgxHierarchicalGridModule,
IgxSwitchModule
} from "igniteui-angular" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
HierarchicalGridValidatorServiceComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxHierarchicalGridModule,
IgxSwitchModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, OnInit, ViewChild } from '@angular/core' ;
import { IgxHierarchicalGridComponent } from 'igniteui-angular' ;
import { CUSTOMERS } from '../../data/hierarchical-data' ;
@Component ({
selector : 'app-hierarchical-grid-validator-service' ,
styleUrls : ['./hierarchical-grid-validator-service.component.scss' ],
templateUrl : './hierarchical-grid-validator-service.component.html'
})
export class HierarchicalGridValidatorServiceComponent implements OnInit {
@ViewChild ('hierarchicalGrid' , { static : true })
private hierarchicalGrid: IgxHierarchicalGridComponent;
public rowEdit: boolean = false ;
public ngOnInit(): void {
this .hierarchicalGrid.data = CUSTOMERS;
}
}
ts コピー <div class ="top-row" >
<igx-switch [(ngModel )]="rowEdit" > Row edit</igx-switch >
</div >
<div class ="grid-wrapper" >
<igx-hierarchical-grid #hierarchicalGrid height ="570px" width ="100%" [primaryKey ]="'CustomerID'"
[batchEditing ]="true" [rowEditable ]="true" [rowEditable ]="rowEdit" >
<igx-column field ="CustomerID" [editable ]="true" > </igx-column >
<igx-column field ="CompanyName" [editable ]="true" required > </igx-column >
<igx-column field ="ContactName" [editable ]="true" required >
</igx-column >
<igx-column field ="ContactTitle" [editable ]="true" required > </igx-column >
<igx-column field ="Phone" [editable ]="true" required > </igx-column >
<igx-column field ="Fax" [editable ]="true" > </igx-column >
<igx-row-island [height ]="null" [key ]="'Orders'" [primaryKey ]="'OrderID'" [autoGenerate ]="false"
[batchEditing ]="true" [rowEditable ]="true" [rowEditable ]="rowEdit" >
<igx-column [editable ]="false" field ="OrderID" > </igx-column >
<igx-column [editable ]="true" field ="EmployeeID" [editable ]="true" required > </igx-column >
<igx-column [editable ]="true" field ="OrderDate" [dataType ]="'date'" [editable ]="true" required >
</igx-column >
<igx-column [editable ]="true" field ="RequiredDate" [dataType ]="'date'" [editable ]="true" required >
</igx-column >
<igx-column field ="ShippedDate" [dataType ]="'date'" [editable ]="true" >
</igx-column >
<igx-column field ="ShipVia" [selectable ]="false" [editable ]="true" min ="0" max ="10" > </igx-column >
<igx-column field ="Freight" [selectable ]="false" [editable ]="true" > </igx-column >
<igx-row-island [height ]="null" [key ]="'OrderDetails'" [primaryKey ]="'ProductID'" [autoGenerate ]="false"
[batchEditing ]="true" [rowEditable ]="true" [primaryKey ]="'ProductID'" [rowEditable ]="rowEdit" >
<igx-column [editable ]="true" field ="ProductID" [editable ]="false" required > </igx-column >
<igx-column [editable ]="true" field ="UnitPrice" [editable ]="true" required > </igx-column >
<igx-column [editable ]="true" field ="Quantity" [editable ]="true" required > </igx-column >
<igx-column [editable ]="true" field ="Discount" [editable ]="true" > </igx-column >
</igx-row-island >
</igx-row-island >
</igx-hierarchical-grid >
</div >
html コピー .top-row , .grid-wrapper {
padding : 16px ;
}
scss コピー
このサンプルが気に入りましたか? 完全な Ignite UI for Angularツールキットにアクセスして、すばやく独自のアプリの作成を開始します。無料でダウンロードできます。
リアクティブ フォームで構成する
formGroupCreated
イベントを介して行/セルで編集を開始するときに検証に使用する FormGroup
を公開します。関連するフィールドに独自の検証を追加して変更できます。
<igx-hierarchical-grid (formGroupCreated )='formCreateHandler($event)' ... >
html
public formCreateHandler (args: IGridFormGroupCreatedEventArgs ) {
const formGroup = args.formGroup;
const orderDateRecord = formGroup.get('OrderDate' );
const requiredDateRecord = formGroup.get('RequiredDate' );
const shippedDateRecord = formGroup.get('ShippedDate' );
orderDateRecord.addValidators(this .futureDateValidator());
requiredDateRecord.addValidators(this .pastDateValidator());
shippedDateRecord.addValidators(this .pastDateValidator());
}
ts
独自の検証関数を作成するか、組み込みの Angular 検証関数 を使用できます。
検証サービス API
グリッドは、validation
プロパティを介して検証サービスを公開します。
このサービスには以下のパブリック API があります。
valid
- グリッドの検証状態が有効であるかどうかを返します。
getInvalid
- 無効な状態のレコードを返します。
clear
- レコードの状態を ID でクリアします。ID が提供されない場合はすべてのレコードの状態をクリアします。
markAsTouched
- 関連するレコード/フィールドをタッチ済みとしてマークします。
無効な状態は、検証ルールに従って検証エラーが修正されるか、クリアされるまで保持されます。
検証トリガー
検証は以下のシナリオでトリガーされます。
注: ユーザー入力または編集 API で編集されていないレコードに対しては、検証はトリガーされません。セルの視覚的なインジケーターは、ユーザー操作または検証サービスの markAsTouched
API を介して入力がタッチ済みと見なされる場合のみ表示されます。
Angular Hierarchical Grid 検証のカスタマイズ オプション
カスタム検証を設定する
テンプレート内の <igx-column>
で使用する独自の検証ディレクティブを定義することができます。
@Directive ({
selector : '[phoneFormat]' ,
providers : [{ provide : NG_VALIDATORS, useExisting : PhoneFormatDirective, multi : true }]
})
export class PhoneFormatDirective extends Validators {
@Input ('phoneFormat' )
public phoneFormatString = '' ;
public validate(control: AbstractControl): ValidationErrors | null {
return this .phoneFormatString ? phoneFormatValidator(new RegExp (this .phoneFormatString, 'i' ))(control)
: null ;
}
}
ts
定義して app モジュールに追加した以降、宣言的にグリッドの指定の列に設定できます。
<igx-column phoneFormat ="\+\d{1}\-(?!0)(\d{3})\-(\d{3})\-(\d{4})\b" ... >
html
デフォルトのエラー テンプレートを変更する
セルが無効な状態になったときにエラー ツールチップに表示されるカスタム エラー テンプレートを定義できます。
これは、カスタム エラー メッセージを追加したり、メッセージの外観やコンテンツを変更したりする場合に便利です。
<igx-column ... >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ="defaultErrorTemplate" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
<div *ngIf ="cell.validation.errors?.['phoneFormat']" >
Please enter correct phone format
</div >
</ng-template >
</igx-column >
html
無効な状態での編集モードの終了を防止する
場合によっては、データ中の無効な値を送信しないようにしたいことがあります。
その場合は、cellEdit
または rowEdit
イベントを使用し、新しい値が無効な場合にイベントをキャンセルできます。
いずれのイベントも引数には valid
プロパティがあり、これによってキャンセルできます。その使用方法は、クロス フィールド検証の例 で確認できます。
<igx-hierarchical-grid (cellEdit )='cellEdit($event)' ... >
html
public cellEdit (evt ) {
if (!evt.valid) {
evt.cancel = true ;
}
}
ts
例
以下の例は、上記のカスタマイズ オプションを示しています。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { HierarchicalGridValidatorServiceExtendedComponent } from "./hierarchical-grid/hierarchical-grid-validator-service-extended/hierarchical-grid-validator-service-extended.component" ;
import { IgxHierarchicalGridModule } from "igniteui-angular" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
HierarchicalGridValidatorServiceExtendedComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxHierarchicalGridModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, Directive, Input, ViewChild } from '@angular/core' ;
import { AbstractControl, NG_VALIDATORS, ValidationErrors, ValidatorFn, Validators } from '@angular/forms' ;
import { IgxHierarchicalGridComponent, IgxRowIslandComponent } from 'igniteui-angular' ;
import { IGridFormGroupCreatedEventArgs } from 'igniteui-angular/lib/grids/common/grid.interface' ;
import { CUSTOMERS } from '../../data/hierarchical-data' ;
export function phoneFormatValidator (phoneReg: RegExp ): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const match = phoneReg.test(control.value);
return match ? null : { phoneFormat : { value : control.value } } ;
}
}
@Directive ({
selector : '[phoneFormat]' ,
providers : [{ provide : NG_VALIDATORS, useExisting : HGridPhoneFormatDirective, multi : true }]
})
export class HGridPhoneFormatDirective extends Validators {
@Input ('phoneFormat' )
public phoneFormatString = '' ;
public validate(control: AbstractControl): ValidationErrors | null {
return this .phoneFormatString ? phoneFormatValidator(new RegExp (this .phoneFormatString, 'i' ))(control)
: null ;
}
}
export function unique (value, index, self ) {
return self.findIndex(v => v.CustomerID === value.CustomerID) === index;
}
@Component ({
selector : 'app-hierarchical-grid-validator-service-extended' ,
styleUrls : ['./hierarchical-grid-validator-service-extended.component.scss' ],
templateUrl : './hierarchical-grid-validator-service-extended.component.html'
})
export class HierarchicalGridValidatorServiceExtendedComponent {
@ViewChild ('hierarchicalGrid' , { static : true })
private hierarchicalGrid: IgxHierarchicalGridComponent;
@ViewChild ('childGrid' , { static : true })
private childGrid: IgxRowIslandComponent;
public data = CUSTOMERS.filter(unique);
public formCreateHandler (formGroupArgs: IGridFormGroupCreatedEventArgs ) {
const orderDateRecord = formGroupArgs.formGroup.get('OrderDate' );
const requiredDateRecord = formGroupArgs.formGroup.get('RequiredDate' );
const shippedDateRecord = formGroupArgs.formGroup.get('ShippedDate' );
orderDateRecord?.addValidators(this .futureDateValidator());
requiredDateRecord?.addValidators([this .futureDateValidator(), this .pastDateValidator()]);
shippedDateRecord?.addValidators([this .futureDateValidator(), this .pastDateValidator()]);
}
public get hasTransactions (): boolean {
return this .hierarchicalGrid.transactions.getAggregatedChanges(false ).length > 0 || this .hasChildTransactions;
}
public get hasChildTransactions (): boolean {
return this .childGrid.gridAPI.getChildGrids()
.find(c => c.transactions.getAggregatedChanges(false ).length > 0 ) !== undefined ;
}
public commit ( ) {
const invalidParentTransactions = this .hierarchicalGrid.validation.getInvalid();
let invalidChildTransactions = [];
this .childGrid.gridAPI.getChildGrids().forEach((grid ) => {
invalidChildTransactions = [... invalidChildTransactions, ...grid.validation.getInvalid()];
});
const invalidTransactions = [...invalidParentTransactions, ...invalidChildTransactions];
if (invalidTransactions.length > 0 && !confirm('You\'re committing invalid transactions. Are you sure?' )) {
return ;
}
this .hierarchicalGrid.transactions.commit(this .data);
this .childGrid.gridAPI.getChildGrids().forEach((grid ) => {
grid.transactions.commit(grid.data);
grid.validation.clear();
});
this .hierarchicalGrid.validation.clear();
}
public undo (grid: any ) {
grid.endEdit(true );
grid.transactions.undo();
}
public redo (grid: any ) {
grid.endEdit(true );
grid.transactions.redo();
}
public futureDateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const date = control.value;
if (date > new Date ()){
return { futureDate : { value : control.value } };
}
return null ;
}
}
public pastDateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const date = control.value;
let pastDate = new Date ('Nov 5 2010' );
if (pastDate){
return pastDate < date ? null : { pastDate : { value : control.value } }
} else return null ;
}
}
}
ts コピー <div class ="grid-wrapper" >
<igx-hierarchical-grid #hierarchicalGrid [height ]="'570px'" [width ]="'100%'" [data ]="data"
[primaryKey ]="'CustomerID'" [batchEditing ]="true" [rowEditable ]="true"
(formGroupCreated )="formCreateHandler($event)" >
<igx-column field ="CustomerID" [editable ]="false" > </igx-column >
<igx-column field ="CompanyName" [editable ]="true" required > </igx-column >
<igx-column field ="ContactName" [editable ]="true" required > </igx-column >
<igx-column field ="ContactTitle" [editable ]="true" required > </igx-column >
<igx-column field ="Phone" [editable ]="true" required phoneFormat ="^[^a-zA-Z]*$" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ="defaultErrorTemplate" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
<div *ngIf ="cell.validation.errors?.['phoneFormat']" >
Please enter correct phone format
</div >
</ng-template >
</igx-column >
<igx-column field ="Fax" [editable ]="true" > </igx-column >
<igx-row-island #childGrid [height ]="null" [key ]="'Orders'" [primaryKey ]="'OrderID'" [autoGenerate ]="false"
[batchEditing ]="true" [rowEditable ]="true" (formGroupCreated )="formCreateHandler($event)" >
<igx-grid-toolbar [grid ]="grid" *igxGridToolbar ="let grid" >
<button igxButton [disabled ]="!grid.transactions.canUndo" (click )="undo(grid)" > Undo</button >
<button igxButton [disabled ]="!grid.transactions.canRedo" (click )="redo(grid)" > Redo</button >
</igx-grid-toolbar >
<igx-column [editable ]="false" field ="OrderID" > </igx-column >
<igx-column [editable ]="true" field ="EmployeeID" [editable ]="true" required > </igx-column >
<igx-column [editable ]="true" field ="OrderDate" [dataType ]="'date'" [editable ]="true" required >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ="defaultErrorTemplate" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
<div *ngIf ="cell.validation.errors?.['futureDate']" >
The date cannot be in the future.
</div >
</ng-template >
</igx-column >
<igx-column [editable ]="true" field ="RequiredDate" [dataType ]="'date'" [editable ]="true" required >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ="defaultErrorTemplate" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
<div *ngIf ="cell.validation.errors?.['futureDate']" >
The date cannot be in the future.
</div >
<div *ngIf ="cell.validation.errors?.['pastDate']" >
The date cannot be before the 5th of November 2010
</div >
</ng-template >
</igx-column >
<igx-column field ="ShippedDate" [dataType ]="'date'" [editable ]="true" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ="defaultErrorTemplate" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
<div *ngIf ="cell.validation.errors?.['futureDate']" >
The date cannot be in the future.
</div >
<div *ngIf ="cell.validation.errors?.['pastDate']" >
The date cannot be before the 5th of November 2010
</div >
</ng-template >
</igx-column >
<igx-column field ="ShipVia" [selectable ]="false" [editable ]="true" > </igx-column >
<igx-column field ="Freight" [selectable ]="false" [editable ]="true" > </igx-column >
</igx-row-island >
</igx-hierarchical-grid >
<div class ="buttons-wrapper" >
<button igxButton [disabled ]="!hierarchicalGrid.transactions.canUndo" (click )="undo(hierarchicalGrid)" > Undo</button >
<button igxButton [disabled ]="!hierarchicalGrid.transactions.canRedo" (click )="redo(hierarchicalGrid)" > Redo</button >
<button igxButton [disabled ]="!hasTransactions" (click )="commit()" > Commit</button >
</div >
</div >
html コピー .top-row , .grid-wrapper {
padding : 16px ;
}
.buttons-wrapper {
display : flex;
flex-direction : row;
justify-content : left;
padding : 10px 0 ;
}
scss コピー
クロス フィールド検証
場合によっては、1 つのフィールドの検証がレコード内の別のフィールドの値に依存することがあります。
その場合、カスタム検証を使用して共有 FormGroup
を介してレコード内の値を比較できます。
クロス フィールド検証は、formGroupCreated
イベントで formGroup に追加できます。その中で複数のフィールドの有効状態を比較できます。
public formCreateCustomerHandler (event: IGridFormGroupCreatedEventArgs ) {
const formGroup = event.formGroup;
formGroup.addValidators(this .addressValidator());
}
public formCreateOrderHandler (event: IGridFormGroupCreatedEventArgs ) {
const formGroup = event.formGroup;
formGroup.addValidators(this .dateValidator());
}
public addressValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const formGroup = control;
let returnObject = {};
const city = formGroup.get('City' );
const country = formGroup.get('Country' );
const validCities = this .countryData.get(country.value);
if (!validCities || !validCities[city.value]) {
returnObject['invalidAddress' ] = true ;
}
return returnObject;
}
}
public dateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const formGroup = control;
let returnObject = {};
const orderDate = formGroup.get('OrderDate' ).value;
const shippedDate = formGroup.get('ShippedDate' ).value;
if (new Date (shippedDate) < new Date (orderDate)) {
returnObject['invalidRange' ] = true ;
}
return returnObject;
}
}
ts
複数フィールド エラーは別の固定列に表示できます。
<igx-column field ="row_valid" header =" " [editable ]="false" [dataType ]="'number'" [pinned ]="true" [width ]="'50px'" >
<ng-template igxCell let-cell ="cell" >
<div *ngIf ="isRowValid(cell)" [igxTooltipTarget ]="tooltipRef"
>
<img width ="18" src ="assets/images/grid/active.png" />
</div >
<div *ngIf ="!isRowValid(cell)" [igxTooltipTarget ]="tooltipRef"
>
<img width ="18" src ="assets/images/grid/expired.png" />
</div >
<div #tooltipRef ="tooltip" igxTooltip [style.width ]="'max-content'" >
<div *ngFor ="let message of stateMessage(cell)" >
{{message}}
</div >
</div >
</ng-template >
</igx-column >
html
エラーと詳細メッセージは、行とセルの有効性に基づいて決定できます。
public isRowValid (cell: CellType ) {
const hasErrors = !!cell.row.validation.errors || cell.row.cells.some(x => !!x.validation.errors);
return !hasErrors;
}
public stateMessage (cell: CellType ) {
const messages = [];
const row = cell.row;
if (row.validation.errors?.invalidAddress) {
messages.push('The address information is invalid. City does not match the Country.' );
}
if (row.validation.errors?.invalidRange) {
messages.push('The ShippedDate cannot be before the OrderDate.' );
}
const cellValidationErrors = row.cells.filter(x => !!x.validation.errors);
if (cellValidationErrors && cellValidationErrors.length > 0 ) {
const fields = cellValidationErrors.map(x => x.column.field).join(',' );
messages.push('The following fields are required: ' + fields);
}
if (messages.length === 0 ) {
return ['Valid' ];
}
return messages;
}
ts
クロス フィールドの例
以下のサンプルは、ルート データと子データの両方について、階層グリッドでのクロス フィールド検証を示しています。
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { HierarchicalGridValidatorServiceCrossCellComponent } from "./hierarchical-grid/hierarchical-grid-cross-field-validation/hierarchical-grid-cross-field-validation.component" ;
import {
IgxHierarchicalGridModule,
IgxTooltipModule
} from "igniteui-angular" ;
import { ReactiveFormsModule } from "@angular/forms" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
HierarchicalGridValidatorServiceCrossCellComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxHierarchicalGridModule,
IgxTooltipModule,
ReactiveFormsModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, OnInit, ViewChild } from '@angular/core' ;
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms' ;
import { CellType, IgxHierarchicalGridComponent, IGridEditEventArgs } from 'igniteui-angular' ;
import { IGridFormGroupCreatedEventArgs } from 'igniteui-angular/lib/grids/common/grid.interface' ;
import { CUSTOMERS } from '../../data/hierarchical-data' ;
@Component ({
selector : 'hierarchical-grid-cross-field-validation' ,
styleUrls : ['./hierarchical-grid-cross-field-validation.component.scss' ],
templateUrl : 'hierarchical-grid-cross-field-validation.component.html'
})
export class HierarchicalGridValidatorServiceCrossCellComponent implements OnInit {
public rowEdit: boolean = true ;
public localdata;
public countryData: Map <string , object >;
public countries = [];
public cities = [];
@ViewChild ('hierarchicalGrid' , { read : IgxHierarchicalGridComponent })
public grid: IgxHierarchicalGridComponent;
constructor ( ) {
}
public ngOnInit(): void {
this .localdata = CUSTOMERS.filter((rec, index, arr ) => arr.findIndex(x => x.CustomerID === rec.CustomerID) === index);
this .countryData = new Map (this .localdata.map(i => [i.Country, {}]));
this .localdata.forEach(rec => {
const country = rec.Country;
const city = rec.City;
this .countryData.get(country)[city] = city;
});
this .countries = [...new Set (this .localdata.map(x => x.Country))];
this .cities = [...new Set (this .localdata.map(x => x.City))];
}
public editHandler (event: IGridEditEventArgs ) {
if (!event.valid) {
event.cancel = true ;
}
}
public formCreateCustomerHandler (event: IGridFormGroupCreatedEventArgs ) {
const formGroup = event.formGroup;
formGroup.addValidators(this .addressValidator());
}
public formCreateOrderHandler (event: IGridFormGroupCreatedEventArgs ) {
const formGroup = event.formGroup;
formGroup.addValidators(this .dateValidator());
}
public addressValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const formGroup = control;
let returnObject = {};
const city = formGroup.get('City' );
const country = formGroup.get('Country' );
const validCities = this .countryData.get(country.value);
if (!validCities || !validCities[city.value]) {
returnObject['invalidAddress' ] = true ;
}
return returnObject;
}
}
public dateValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const formGroup = control;
let returnObject = {};
const orderDate = formGroup.get('OrderDate' ).value;
const shippedDate = formGroup.get('ShippedDate' ).value;
if (new Date (shippedDate) < new Date (orderDate)) {
returnObject['invalidRange' ] = true ;
}
return returnObject;
}
}
public isRowValid (cell: CellType ) {
const hasErrors = !!cell.row.validation.errors || cell.row.cells.some(x => !!x.validation.errors);
return !hasErrors;
}
public stateMessage (cell: CellType ) {
const messages = [];
const row = cell.row;
if (row.validation.errors?.invalidAddress) {
messages.push('The address information is invalid. City does not match the Country.' );
}
if (row.validation.errors?.invalidRange) {
messages.push('The ShippedDate cannot be before the OrderDate.' );
}
const cellValidationErrors = row.cells.filter(x => !!x.validation.errors);
if (cellValidationErrors && cellValidationErrors.length > 0 ) {
const fields = cellValidationErrors.map(x => x.column.field).join(',' );
messages.push('The following fields are required: ' + fields);
}
if (messages.length === 0 ) {
return ['Valid' ];
}
return messages;
}
public commit (grid: any ) {
const hGrid = grid as IgxHierarchicalGridComponent;
const invalidTransactions = hGrid.validation.getInvalid();
if (invalidTransactions.length > 0 && !confirm('You\'re commiting invalid transactions. Are you sure?' )) {
return ;
}
hGrid.transactions.commit(hGrid.data);
hGrid.validation.clear();
}
}
ts コピー <div class ="top-row" >
<igx-switch [(ngModel )]="rowEdit" > Row edit</igx-switch >
</div >
<div class ="grid__wrapper" >
<igx-hierarchical-grid [igxPreventDocumentScroll ]="true" [primaryKey ]="'CustomerID'" [rowEditable ]="rowEdit" [batchEditing ]="true"
#hierarchicalGrid [data ]="localdata" [height ]="'500px'" [width ]="'100%'" displayDensity ="compact"
(formGroupCreated )="formCreateCustomerHandler($event)" (cellEdit )="editHandler($event)" (rowEdit )="editHandler($event)" >
<igx-column field ="row_valid" header =" " [editable ]="false" [dataType ]="'number'" [pinned ]="true" [width ]="'50px'" >
<ng-template igxCell let-cell ="cell" >
<div *ngIf ="isRowValid(cell)" [igxTooltipTarget ]="tooltipRef" >
<img width ="18" src ="https://www.infragistics.com/angular-demos-lob/assets/images/grid/active.png" />
</div >
<div *ngIf ="!isRowValid(cell)" [igxTooltipTarget ]="tooltipRef" >
<img width ="18" src ="https://www.infragistics.com/angular-demos-lob/assets/images/grid/expired.png" />
</div >
<div #tooltipRef ="tooltip" igxTooltip [style.width ]="'max-content'" >
<div *ngFor ="let message of stateMessage(cell)" >
{{message}}
</div >
</div >
</ng-template >
</igx-column >
<igx-column field ="CustomerID" [hidden ]="true" > </igx-column >
<igx-column field ="ContactName" [editable ]="true" required > </igx-column >
<igx-column field ="ContactTitle" [editable ]="true" required > </igx-column >
<igx-column field ="City" [editable ]="true" >
<ng-template igxCellEditor let-cell ="cell" let-value let-fc ='formControl' >
<igx-select [formControl ]="fc" [igxFocus ]="true" >
<igx-select-item *ngFor ="let city of cities" [value ]="city" >
{{ city }}
</igx-select-item >
</igx-select >
</ng-template >
</igx-column >
<igx-column field ="Country" [editable ]="true" >
<ng-template igxCellEditor let-cell ="cell" let-value let-fc ='formControl' >
<igx-select [formControl ]="fc" [igxFocus ]="true" >
<igx-select-item *ngFor ="let country of countries" [value ]="country" >
{{ country }}
</igx-select-item >
</igx-select >
</ng-template >
</igx-column >
<igx-column field ="PostalCode" [editable ]="true" required > </igx-column >
<igx-column field ="Phone" [editable ]="true" required > </igx-column >
<igx-row-island [primaryKey ]="'OrderID'" [height ]="null" [key ]="'Orders'" [autoGenerate ]="false" [rowEditable ]="rowEdit"
(formGroupCreated )='formCreateOrderHandler($event)' (cellEdit )="editHandler($event)" (rowEdit )="editHandler($event)" >
<igx-grid-toolbar [grid ]="grid" *igxGridToolbar ="let grid" >
<button igxButton [disabled ]="grid.transactions.getAggregatedChanges(false).length < 1" (click )="commit(grid)" > Commit</button >
</igx-grid-toolbar >
<igx-column field ="OrderID" [hidden ]="true" > </igx-column >
<igx-column field ="EmployeeID" [hidden ]="true" > </igx-column >
<igx-column field ="row_valid" header =" " [editable ]="false" [dataType ]="'number'" [pinned ]="true" [width ]="'50px'" >
<ng-template igxCell let-cell ="cell" >
<div *ngIf ="isRowValid(cell)" [igxTooltipTarget ]="tooltipRef" >
<img width ="18" src ="https://www.infragistics.com/angular-demos-lob/assets/images/grid/active.png" />
</div >
<div *ngIf ="!isRowValid(cell)" [igxTooltipTarget ]="tooltipRef" >
<img width ="18" src ="https://www.infragistics.com/angular-demos-lob/assets/images/grid/expired.png" />
</div >
<div #tooltipRef ="tooltip" igxTooltip [style.width ]="'max-content'" >
<div *ngFor ="let message of stateMessage(cell)" >
{{message}}
</div >
</div >
</ng-template >
</igx-column >
<igx-column field ="OrderDate" [dataType ]="'date'" [editable ]="true" required > </igx-column >
<igx-column field ="ShippedDate" [dataType ]="'date'" [editable ]="true" required > </igx-column >
<igx-column field ="ShipVia" [editable ]="true" required > </igx-column >
<igx-column field ="Freight" [editable ]="true" required > </igx-column >
<igx-column field ="ShipName" [editable ]="true" required > </igx-column >
</igx-row-island >
</igx-hierarchical-grid >
<div class ="buttons-wrapper" >
<button igxButton [disabled ]="hierarchicalGrid.transactions.getAggregatedChanges(false).length < 1" (click )="commit(hierarchicalGrid)" > Commit</button >
</div >
</div >
html コピー
.top-row , .grid__wrapper {
padding : 16px ;
padding-bottom : 0 ;
}
.buttons-wrapper {
display : flex;
flex-direction : row;
justify-content : start;
padding : 10px 0 ;
}
scss コピー
スタイル設定
Ignite UI for Angular テーマ ライブラリ を使用して、編集時のデフォルトの検証スタイルを変更できます。
以下の例では、検証メッセージの公開されたテンプレートを使用します。ツールチップをポップアウトし、および、検証のデフォルトの外観を変更するためにエラー時の色をオーバーライドします。
また、無効な行をより明確にするために背景のスタイルを設定します。
テーマのインポート
スタイルを設定し、css 変数にアクセスする最も簡単な方法は、app
のグローバル スタイル ファイル (通常 は styles.scss
です) でスタイルを定義することです。
はじめに themes/index
ファイルをインポートすることにより、Ignite UI for Angular Sass フレームワークの強力なツールへアクセスできるようになります。
@use "igniteui-angular/theming" as *;
scss
スタイルを含める
エラーの色を変更するには、css 変数 --igx-error-500
を使用します。
--igx-error-500: 34 , 80% , 63% ;
scss
カスタム テンプレート
デフォルトのエラー テンプレートを変更することで、カスタム クラスとスタイルを設定できます。
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
html
無効な行とセルのスタイル
行とセルは、開発者が行またはセルが無効かどうか、およびアクティブなエラーの種類を知るための API を提供します。
public rowStyles = {
background : (row: RowType ) => row.validation.status === 'INVALID' ? '#FF000033' : '#00000000'
};
public cellStyles = {
'invalid-cell' : (rowData, columnKey ) => {
let cell = this .hierarchicalGrid.getCellByKey(rowData, columnKey);
if (!cell) {
for (let grid of this .childGrid.gridAPI.getChildGrids()) {
cell = grid.getCellByKey(rowData, columnKey);
if (cell) break ;
}
}
return cell && cell.validation.status === 'INVALID' ;
}
}
ts
<igx-hierarchical-grid [rowStyles ]="rowStyles" >
<igx-column field ="Artist" [editable ]="true" [dataType ]="'string'" required [cellClasses ]="cellStyles" >
...
<igx-row-island [key ]="'Albums'" [rowStyles ]="rowStyles" >
<igx-column field ="Album" [editable ]="true" [dataType ]="'string'" required [cellClasses ]="cellStyles" >
html
デモ
import { NgModule } from "@angular/core" ;
import { FormsModule } from "@angular/forms" ;
import { BrowserModule } from "@angular/platform-browser" ;
import { BrowserAnimationsModule } from "@angular/platform-browser/animations" ;
import { AppComponent } from "./app.component" ;
import { IgxHierarchicalGridModule } from "igniteui-angular" ;
import { HGridValidationStyleComponent } from "./hierarchical-grid/hierarchical-grid-validation-style/hierarchical-grid-validation-style.component" ;
import { IgxPreventDocumentScrollModule } from "./directives/prevent-scroll.directive" ;
@NgModule ({
bootstrap : [AppComponent],
declarations : [
AppComponent,
HGridValidationStyleComponent
],
imports : [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
IgxPreventDocumentScrollModule,
IgxHierarchicalGridModule
],
providers : [],
entryComponents : [],
schemas : []
})
export class AppModule {}
ts コピー import { Component, ViewChild } from '@angular/core' ;
import { IgxGridComponent, IgxHierarchicalGridComponent, IgxRowIslandComponent, RowType } from 'igniteui-angular' ;
import { SINGERS } from '../../data/singersData' ;
import { Singer } from '../models' ;
@Component ({
selector : 'app-hierarchical-grid-validation-style' ,
styleUrls : ['./hierarchical-grid-validation-style.component.scss' ],
templateUrl : 'hierarchical-grid-validation-style.component.html'
})
export class HGridValidationStyleComponent {
@ViewChild ('hierarchicalGrid' , { read : IgxHierarchicalGridComponent, static : true }) public hierarchicalGrid: IgxHierarchicalGridComponent;
@ViewChild ('childGrid' , { static : true }) private childGrid: IgxRowIslandComponent;
public localData: Singer[];
public rowStyles = {
background : (row: RowType ) => row.validation.status === 'INVALID' ? '#FF000033' : '#00000000'
};
public cellStyles = {
'invalid-cell' : (rowData, columnKey ) => {
let cell = this .hierarchicalGrid.getCellByKey(rowData, columnKey);
if (!cell) {
for (let grid of this .childGrid.gridAPI.getChildGrids()) {
cell = (grid as IgxGridComponent).getCellByKey(rowData, columnKey);
if (cell) break ;
}
}
return cell && cell.validation.status === 'INVALID' ;
}
}
constructor ( ) {
this .localData = SINGERS;
}
public formatter = (a ) => a;
}
ts コピー <div class ="grid__wrapper" >
<igx-hierarchical-grid [igxPreventDocumentScroll ]="true" #hierarchicalGrid class ="hgrid" [data ]="localData" [autoGenerate ]="false" [height ]="'600px'"
[width ]="'100%'" [rowStyles ]="rowStyles" >
<igx-column field ="Artist" [editable ]="true" [dataType ]="'string'" required [cellClasses ]="cellStyles" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
</igx-column >
<igx-column field ="HasGrammyAward" header ="Has Grammy Award?" [editable ]="true" [dataType ]="'boolean'" >
</igx-column >
<igx-column field ="Debut" [editable ]="true" dataType ="number" [formatter ]="formatter" > </igx-column >
<igx-column field ="GrammyNominations" header ="Grammy Nominations" [editable ]="true" dataType ="number" > </igx-column >
<igx-column field ="GrammyAwards" header ="Grammy Awards" [editable ]="true" dataType ="number" > </igx-column >
<igx-row-island #childGrid [height ]="null" [key ]="'Albums'" [autoGenerate ]="false" [rowStyles ]="rowStyles" >
<igx-column field ="Album" [editable ]="true" [dataType ]="'string'" required [cellClasses ]="cellStyles" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
</igx-column >
<igx-column field ="LaunchDate" header ="Launch Date" [editable ]="true" [dataType ]="'date'" required [cellClasses ]="cellStyles" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
</igx-column >
<igx-column field ="BillboardReview" header ="Billboard Review" [editable ]="true" dataType ="number" required [cellClasses ]="cellStyles" >
<ng-template igxCellValidationError let-cell ='cell' let-defaultErr ='defaultErrorTemplate' >
<div class ="validator-container" >
<ng-container *ngTemplateOutlet ="defaultErr" >
</ng-container >
</div >
</ng-template >
</igx-column >
<igx-column field ="USBillboard200" header ="US Billboard 200" [editable ]="true" dataType ="number" > </igx-column >
</igx-row-island >
</igx-hierarchical-grid >
</div >
html コピー @use '../../../variables' as *;
.grid__wrapper {
margin : 0 auto;
padding : 16px ;
}
.validator-container {
color : hsl(34 , 80% , 63% );
}
:host ::ng-deep {
--ig-error-500 : 34deg , 80% , 63% ;
.invalid-cell {
box-shadow : inset 0 0 0 0.125rem color($color : error);
}
}
scss コピー
API リファレンス
既知の問題と制限
制限
説明
validationTrigger
が blur の場合、editValue
と検証は、エディターからフォーカスが外れた後にのみトリガーされます。
理由は、これが formControl の updateOn
プロパティを利用しているためです。これにより、formControl が更新され、関連する検証をトリガーするイベントが決定されます。
その他のリソース
コミュニティに参加して新しいアイデアをご提案ください。