UNPKG

@progress/kendo-angular-spreadsheet

Version:

A Spreadsheet Component for Angular

469 lines (468 loc) 29.2 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { NgIf } from '@angular/common'; import { Component, EventEmitter, NgZone, Output } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { DropDownListComponent } from '@progress/kendo-angular-dropdowns'; import { CheckBoxComponent, RadioButtonComponent, TextBoxComponent } from '@progress/kendo-angular-inputs'; import { LocalizationService } from '@progress/kendo-angular-l10n'; import { rangeValidator } from './utils'; import { SpreadsheetService } from '../common/spreadsheet.service'; import { isPresent } from '@progress/kendo-angular-common'; import { LabelComponent } from '@progress/kendo-angular-label'; import { DialogActionsComponent, DialogContentBase, DialogRef } from '@progress/kendo-angular-dialog'; import { ButtonComponent } from '@progress/kendo-angular-buttons'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; import * as i2 from "../common/spreadsheet.service"; import * as i3 from "@progress/kendo-angular-dialog"; import * as i4 from "@angular/forms"; const COMPARER_VALUE_NUMBER = { greaterThan: 'validationMinValue', lessThan: 'validationMaxValue', greaterThanOrEqualTo: 'validationMinValue', lessThanOrEqualTo: 'validationMaxValue' }; const COMPARER_VALUE_DATE = { greaterThan: 'validationStartValue', lessThan: 'validationEndValue' }; /** * @hidden */ export class DataValidationDialogComponent extends DialogContentBase { localization; spreadsheetService; zone; cellRange; criteriaList; ignoreBlank = true; showButton = true; showHint; from = null; to = null; validationFormGroup; hintTitle = 'Validation Error'; hintMessage = 'The value that you entered violates the validation rules set on the cell.'; _comparer; _criteria; _onInvalidData; onInvalidDataSub; allComparers = []; textComparers = []; dialogAction = new EventEmitter(); constructor(localization, spreadsheetService, zone, dialog) { super(dialog); this.localization = localization; this.spreadsheetService = spreadsheetService; this.zone = zone; } ngOnInit() { if (this.criteria.type === 'any') { this.validationFormGroup = new FormGroup({ range: new FormControl(this.cellRange, [Validators.required, rangeValidator(this)]) }); } else if (this.comparer?.type === 'between' || this.comparer?.type === 'notBetween') { this.validationFormGroup = new FormGroup({ range: new FormControl(this.cellRange, [Validators.required, rangeValidator(this)]), min: new FormControl(this.from, [Validators.required]), max: new FormControl(this.to, [Validators.required]), onInvalidData: new FormControl(this.onInvalidData) }); } else { this.validationFormGroup = new FormGroup({ range: new FormControl(this.cellRange, [Validators.required, rangeValidator(this)]), min: new FormControl(this.from, [Validators.required]), onInvalidData: new FormControl(this.onInvalidData) }); } this.addInvalidDataSub(); this.criteriaList = [ { type: 'any', name: this.getLocalizationMessage('anyValueValidationCriteria') }, { type: 'number', name: this.getLocalizationMessage('numberValidationCriteria') }, { type: 'text', name: this.getLocalizationMessage('textValidationCriteria') }, { type: 'date', name: this.getLocalizationMessage('dateValidationCriteria') }, { type: 'custom', name: this.getLocalizationMessage('customFormulaValidationCriteria') }, { type: 'list', name: this.getLocalizationMessage('listValidationCriteria') } ]; this.allComparers = [ { type: 'greaterThan', name: this.getLocalizationMessage('greaterThanValidationComparer') }, { type: 'lessThan', name: this.getLocalizationMessage('lessThanValidationComparer') }, { type: 'between', name: this.getLocalizationMessage('betweenValidationComparer') }, { type: 'notBetween', name: this.getLocalizationMessage('notBetweenValidationComparer') }, { type: 'equalTo', name: this.getLocalizationMessage('equalToValidationComparer') }, { type: 'notEqualTo', name: this.getLocalizationMessage('notEqualToValidationComparer') }, { type: 'greaterThanOrEqualTo', name: this.getLocalizationMessage('greaterThanOrEqualToValidationComparer') }, { type: 'lessThanOrEqualTo', name: this.getLocalizationMessage('lessThanOrEqualToValidationComparer') } ]; this.textComparers = [ { type: 'equalTo', name: this.getLocalizationMessage('equalToValidationComparer') }, { type: 'notEqualTo', name: this.getLocalizationMessage('notEqualToValidationComparer') } ]; } ngOnDestroy() { this.onInvalidDataSub?.unsubscribe(); this.onInvalidDataSub = null; } getLocalizationMessage(key) { return this.localization.get(key); } onRangeChange(value, input) { let range; try { range = this.spreadsheetService.spreadsheet.activeSheet().range(value); } catch (e) { /** noop */ } if (isPresent(range)) { range.select(); this.cellRange = value; } this.zone.runOutsideAngular(() => { setTimeout(() => { input.focus(); }); }); } onSelectionChange(item, field) { if (field === 'criteria') { this.from = this.to = null; this.comparer = null; this.showButton = this.ignoreBlank = true; if (item.type === 'any') { this.validationFormGroup = new FormGroup({ range: new FormControl(this.cellRange, [Validators.required, rangeValidator(this)]) }); } else { this.validationFormGroup = new FormGroup({ range: new FormControl(this.cellRange, [Validators.required, rangeValidator(this)]), min: new FormControl(this.from, [Validators.required]), onInvalidData: new FormControl(this.onInvalidData) }); } } else { this.showButton = this.ignoreBlank = true; switch (item.type) { case 'between': case 'notBetween': this.validationFormGroup = new FormGroup({ range: new FormControl(this.cellRange, [Validators.required, rangeValidator(this)]), min: new FormControl(this.from, [Validators.required]), max: new FormControl(this.to, [Validators.required]), onInvalidData: new FormControl(this.onInvalidData) }); break; default: this.validationFormGroup = new FormGroup({ range: new FormControl(this.cellRange, [Validators.required, rangeValidator(this)]), min: new FormControl(this.from, [Validators.required]), onInvalidData: new FormControl(this.onInvalidData) }); break; } } this.addInvalidDataSub(); this[field] = item; } get criteria() { return this._criteria || { type: 'any', name: 'Any value' }; } set criteria(value) { this._criteria = value; } get comparer() { return this._comparer || this.comparerList[0]; } set comparer(value) { this._comparer = value; } get onInvalidData() { return this._onInvalidData || 'reject'; } set onInvalidData(value) { this._onInvalidData = value; } get comparerList() { return this.criteria.type === 'text' ? this.textComparers : this.allComparers; } get singleValue() { return !this.comparer.name.includes('between'); } get showComparer() { return this.criteria.type === 'number' || this.criteria.type === 'text' || this.criteria.type === 'date'; } setData(args) { for (const key in args) { this[key] = args[key]; } } getValueLabel() { const valueLabel = this.localization.get('validationValue'); if (this.criteria.type === 'custom' || this.criteria.type === 'list') { return valueLabel; } const labelKey = this.criteria.type === 'date' ? COMPARER_VALUE_DATE[this.comparer.type] : COMPARER_VALUE_NUMBER[this.comparer.type]; return this.localization.get(labelKey) || valueLabel; } getShowButtonLabel() { const labelKey = this.criteria.type === 'list' ? 'validationShowListButtonCheckbox' : 'validationShowDateButtonCheckbox'; return this.getLocalizationMessage(labelKey); } get validationPresent() { const currentSheet = this.spreadsheetService.spreadsheet.activeSheet(); return !!currentSheet.range(currentSheet.activeCell()).validation(); } addInvalidDataSub() { this.onInvalidDataSub?.unsubscribe(); if (this.validationFormGroup.get('onInvalidData')) { this.onInvalidDataSub = this.validationFormGroup.get('onInvalidData').valueChanges.subscribe((value) => { this.onInvalidData = value; }); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DataValidationDialogComponent, deps: [{ token: i1.LocalizationService }, { token: i2.SpreadsheetService }, { token: i0.NgZone }, { token: i3.DialogRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: DataValidationDialogComponent, isStandalone: true, selector: "ng-component", outputs: { dialogAction: "dialogAction" }, usesInheritance: true, ngImport: i0, template: ` <form class="k-form k-form-md" [formGroup]="validationFormGroup"> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationCellRange')" [for]="rangeInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #rangeInput [clearButton]="true" [value]="cellRange" (valueChange)="onRangeChange($event, rangeInput)" formControlName="range"> </kendo-textbox> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationCriteria')" [for]="criteriaInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-dropdownlist #criteriaInput [data]="criteriaList" [value]="criteria" (selectionChange)="onSelectionChange($event, 'criteria')" textField="name" valueField="type"> </kendo-dropdownlist> </div> </div> <ng-container *ngIf="criteria.type !== 'any'"> <div class="k-form-field" *ngIf="showComparer"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationComparer')" [for]="comparerInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-dropdownlist #comparerInput [data]="comparerList" [value]="comparer" (selectionChange)="onSelectionChange($event, 'comparer')" textField="name" valueField="type"> </kendo-dropdownlist> </div> </div> <div class="k-form-field" *ngIf="singleValue; else betweenValues"> <kendo-label class="k-label k-form-label" [text]="getValueLabel()" [for]="minInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #minInput [(value)]="from" formControlName="min"></kendo-textbox> </div> </div> <ng-template #betweenValues> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage(criteria.type === 'date' ? 'validationStartValue' : 'validationMinValue')" [for]="betweenMinInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #betweenMinInput [(value)]="from" formControlName="min"></kendo-textbox> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage(criteria.type === 'date' ? 'validationEndValue' : 'validationMaxValue')" [for]="betweenMaxInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #betweenMaxInput [(value)]="to" formControlName="max"></kendo-textbox> </div> </div> </ng-template> <div class="k-form-field" *ngIf="criteria.type === 'list'"> <div class="k-form-field-wrap"> <kendo-checkbox #showBtnCheckBox [(checkedState)]="showButton"></kendo-checkbox> <kendo-label class="k-checkbox-label" [for]="showBtnCheckBox" [text]="getLocalizationMessage('validationShowListButtonCheckbox')"></kendo-label> </div> </div> <div class="k-form-field"> <div class="k-form-field-wrap"> <kendo-checkbox #ignoreBlankCheckBox [(checkedState)]="ignoreBlank"></kendo-checkbox> <kendo-label class="k-checkbox-label" [for]="ignoreBlankCheckBox" [text]="getLocalizationMessage('validationIgnoreBlankCheckbox')"></kendo-label> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationOnInvalidData')"></kendo-label> <div class="k-form-field-wrap"> <ul class="k-radio-list k-list-vertical"> <li class="k-radio-list-item"> <kendo-radiobutton #rejectRadioBtn formControlName="onInvalidData" value="reject"></kendo-radiobutton> <kendo-label class="k-radio-label" [for]="rejectRadioBtn" [text]="getLocalizationMessage('validationRejectInput')"></kendo-label> </li> <li class="k-radio-list-item"> <kendo-radiobutton #warningRadioBtn formControlName="onInvalidData" value="warning"></kendo-radiobutton> <kendo-label class="k-radio-label" [for]="warningRadioBtn" [text]="getLocalizationMessage('validationShowWarning')"></kendo-label> </li> </ul> </div> </div> <div class="k-form-field"> <div class="k-form-field-wrap"> <kendo-checkbox #showHintCheckBox [(checkedState)]="showHint"></kendo-checkbox> <kendo-label class="k-checkbox-label" [for]="showHintCheckBox" [text]="getLocalizationMessage('validationShowHint')"></kendo-label> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationHintTitle')" [for]="hintTitleInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #hintTitleInput [(value)]="hintTitle"></kendo-textbox> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationHintMessage')" [for]="hintMessageInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #hintMessageInput [(value)]="hintMessage"></kendo-textbox> </div> </div> </ng-container> </form> <kendo-dialog-actions layout="start" #dialogActions> <button kendoButton themeColor="primary" [disabled]="validationFormGroup?.invalid" (click)="dialogAction.emit(getLocalizationMessage('dialogApply'))"> {{getLocalizationMessage('dialogApply')}} </button> <button kendoButton (click)="dialogAction.emit(getLocalizationMessage('dialogCancel'))">{{getLocalizationMessage('dialogCancel')}}</button> <button kendoButton themeColor="error" fillMode="flat" *ngIf="validationPresent" (click)="dialogAction.emit(getLocalizationMessage('dialogRemove'))"> {{getLocalizationMessage('dialogRemove')}} </button> </kendo-dialog-actions> `, isInline: true, dependencies: [{ kind: "component", type: TextBoxComponent, selector: "kendo-textbox", inputs: ["focusableId", "title", "type", "disabled", "readonly", "tabindex", "value", "selectOnFocus", "showSuccessIcon", "showErrorIcon", "clearButton", "successIcon", "successSvgIcon", "errorIcon", "errorSvgIcon", "clearButtonIcon", "clearButtonSvgIcon", "size", "rounded", "fillMode", "tabIndex", "placeholder", "maxlength", "inputAttributes"], outputs: ["valueChange", "inputFocus", "inputBlur", "focus", "blur"], exportAs: ["kendoTextBox"] }, { kind: "component", type: DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "component", type: CheckBoxComponent, selector: "kendo-checkbox", inputs: ["checkedState", "rounded"], outputs: ["checkedStateChange"], exportAs: ["kendoCheckBox"] }, { kind: "component", type: RadioButtonComponent, selector: "kendo-radiobutton", inputs: ["checked"], outputs: ["checkedChange"], exportAs: ["kendoRadioButton"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i4.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: LabelComponent, selector: "kendo-label", inputs: ["text", "for", "optional", "labelCssStyle", "labelCssClass"], exportAs: ["kendoLabel"] }, { kind: "component", type: DialogActionsComponent, selector: "kendo-dialog-actions", inputs: ["actions", "layout"], outputs: ["action"] }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon", "primary", "look"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DataValidationDialogComponent, decorators: [{ type: Component, args: [{ template: ` <form class="k-form k-form-md" [formGroup]="validationFormGroup"> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationCellRange')" [for]="rangeInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #rangeInput [clearButton]="true" [value]="cellRange" (valueChange)="onRangeChange($event, rangeInput)" formControlName="range"> </kendo-textbox> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationCriteria')" [for]="criteriaInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-dropdownlist #criteriaInput [data]="criteriaList" [value]="criteria" (selectionChange)="onSelectionChange($event, 'criteria')" textField="name" valueField="type"> </kendo-dropdownlist> </div> </div> <ng-container *ngIf="criteria.type !== 'any'"> <div class="k-form-field" *ngIf="showComparer"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationComparer')" [for]="comparerInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-dropdownlist #comparerInput [data]="comparerList" [value]="comparer" (selectionChange)="onSelectionChange($event, 'comparer')" textField="name" valueField="type"> </kendo-dropdownlist> </div> </div> <div class="k-form-field" *ngIf="singleValue; else betweenValues"> <kendo-label class="k-label k-form-label" [text]="getValueLabel()" [for]="minInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #minInput [(value)]="from" formControlName="min"></kendo-textbox> </div> </div> <ng-template #betweenValues> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage(criteria.type === 'date' ? 'validationStartValue' : 'validationMinValue')" [for]="betweenMinInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #betweenMinInput [(value)]="from" formControlName="min"></kendo-textbox> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage(criteria.type === 'date' ? 'validationEndValue' : 'validationMaxValue')" [for]="betweenMaxInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #betweenMaxInput [(value)]="to" formControlName="max"></kendo-textbox> </div> </div> </ng-template> <div class="k-form-field" *ngIf="criteria.type === 'list'"> <div class="k-form-field-wrap"> <kendo-checkbox #showBtnCheckBox [(checkedState)]="showButton"></kendo-checkbox> <kendo-label class="k-checkbox-label" [for]="showBtnCheckBox" [text]="getLocalizationMessage('validationShowListButtonCheckbox')"></kendo-label> </div> </div> <div class="k-form-field"> <div class="k-form-field-wrap"> <kendo-checkbox #ignoreBlankCheckBox [(checkedState)]="ignoreBlank"></kendo-checkbox> <kendo-label class="k-checkbox-label" [for]="ignoreBlankCheckBox" [text]="getLocalizationMessage('validationIgnoreBlankCheckbox')"></kendo-label> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationOnInvalidData')"></kendo-label> <div class="k-form-field-wrap"> <ul class="k-radio-list k-list-vertical"> <li class="k-radio-list-item"> <kendo-radiobutton #rejectRadioBtn formControlName="onInvalidData" value="reject"></kendo-radiobutton> <kendo-label class="k-radio-label" [for]="rejectRadioBtn" [text]="getLocalizationMessage('validationRejectInput')"></kendo-label> </li> <li class="k-radio-list-item"> <kendo-radiobutton #warningRadioBtn formControlName="onInvalidData" value="warning"></kendo-radiobutton> <kendo-label class="k-radio-label" [for]="warningRadioBtn" [text]="getLocalizationMessage('validationShowWarning')"></kendo-label> </li> </ul> </div> </div> <div class="k-form-field"> <div class="k-form-field-wrap"> <kendo-checkbox #showHintCheckBox [(checkedState)]="showHint"></kendo-checkbox> <kendo-label class="k-checkbox-label" [for]="showHintCheckBox" [text]="getLocalizationMessage('validationShowHint')"></kendo-label> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationHintTitle')" [for]="hintTitleInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #hintTitleInput [(value)]="hintTitle"></kendo-textbox> </div> </div> <div class="k-form-field"> <kendo-label class="k-form-label" [text]="getLocalizationMessage('validationHintMessage')" [for]="hintMessageInput"></kendo-label> <div class="k-form-field-wrap"> <kendo-textbox #hintMessageInput [(value)]="hintMessage"></kendo-textbox> </div> </div> </ng-container> </form> <kendo-dialog-actions layout="start" #dialogActions> <button kendoButton themeColor="primary" [disabled]="validationFormGroup?.invalid" (click)="dialogAction.emit(getLocalizationMessage('dialogApply'))"> {{getLocalizationMessage('dialogApply')}} </button> <button kendoButton (click)="dialogAction.emit(getLocalizationMessage('dialogCancel'))">{{getLocalizationMessage('dialogCancel')}}</button> <button kendoButton themeColor="error" fillMode="flat" *ngIf="validationPresent" (click)="dialogAction.emit(getLocalizationMessage('dialogRemove'))"> {{getLocalizationMessage('dialogRemove')}} </button> </kendo-dialog-actions> `, standalone: true, imports: [TextBoxComponent, DropDownListComponent, CheckBoxComponent, RadioButtonComponent, NgIf, ReactiveFormsModule, LabelComponent, DialogActionsComponent, ButtonComponent] }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }, { type: i2.SpreadsheetService }, { type: i0.NgZone }, { type: i3.DialogRef }]; }, propDecorators: { dialogAction: [{ type: Output }] } });