@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
984 lines • 136 kB
JavaScript
import { __decorate, __metadata } from "tslib";
import { ChangeDetectorRef, ContentChild, Directive, EventEmitter, HostBinding, Injector, Input, Output, TemplateRef } from '@angular/core';
import { NgControl } from '@angular/forms';
import { Yield } from '@microsoft/windows-admin-center-sdk/core/base/decorators/yield.decorator';
import { Logging } from '@microsoft/windows-admin-center-sdk/core/diagnostics/logging';
import { Dom } from '@microsoft/windows-admin-center-sdk/core/dom/dom';
import { fromEvent, merge, of } from 'rxjs';
import { BaseComponent } from '../../common/base.component';
import { DisabledChangedEvent } from '../../common/events/disabledChanged.event';
import { SME_LAYOUT_PROVIDER } from '../../common/layout';
import { ValidationAlertSeverity } from '../validation-alert/validation-alert';
import { FormFieldAccessorDirective } from './form-field-accessor.directive';
import { SME_FORM_FIELD_AGGREGATOR_PROVIDER } from './form-field-aggregator';
import { FormFieldAsyncValidatorDirective } from './form-field-async-validator.directive';
import { FormFieldOrientation } from './form-field-orientation';
import { FormFieldValidatorDirective } from './form-field-validator.directive';
import * as i0 from "@angular/core";
/**
* Base component class for form fields
* A circular reference would be created by injecting both ngModel
* and providing @see ControlValueAccessor and @see Validator implementations on the same component.
* Therefore, we have broken the FormField component into 3 parts.
* - The @see FormFieldValidatorDirective handles the implementation of @see Validator and @see NG_VALIDATORS
* - The @see FormFieldAccessorDirective handles the implementation of @see ControlValueAccessor and @see NG_VALUE_ACCESSOR
* - This @see FormFieldComponent is our glue that pulls it all together with labels and alerts and injects the NgControl instance safely.
*
* In order for this to work, all three components must use the same base selector @see FormFieldComponent.selector.
* This is enforced in the constructor of this component,
* However the directives are done by convention and will just crash if the injector cannot find them.
*
*/
/**
*
* @smeDoc {@label Form fields @id sme-form-fields-component}
*
* @overview
* @file {@filepath ./examples/overview.md}
*
* @example {@label Search with emphasized search button @id search-emphasis}
* @file {@filename component.ts @filepath ./examples/search-emphasize.component.ts}
* @file {@filename component.html @filepath ./examples/search-emphasize.component.html}
*
* @example {@label Search with hidden label @id search-hidden}
* @file {@filename component.ts @filepath ./examples/search-hidden-label.component.ts}
* @file {@filename component.html @filepath ./examples/search-hidden-label.component.html}
*
* @example {@label Search with required field @id search-required}
* @file {@filename component.ts @filepath ./examples/search-required.component.ts}
* @file {@filename component.html @filepath ./examples/search-required.component.html}
*
* @example {@label Calendar @id calendar}
* @file {@filename component.html @filepath ./examples/calendar.component.html}
*
* @example {@label Clock @id clock}
* @file {@filename component.ts @filepath ./examples/clock.component.ts}
* @file {@filename component.html @filepath ./examples/clock.component.html}
*
* @example {@label Datetime @id datetime}
* @file {@filename component.ts @filepath ./examples/datetime.component.ts}
* @file {@filename component.html @filepath ./examples/datetime.component.html}
*
* @example {@label Validation error @id validation-error}
* @file {@filename component.ts @filepath ./examples/validation-error.component.ts}
* @file {@filename component.html @filepath ./examples/validation-error.component.html}
*
* @example {@label Validation pending @id validation-pending}
* @file {@filename component.ts @filepath ./examples/validation-pending.component.ts}
* @file {@filename component.html @filepath ./examples/validation-pending.component.html}
*
* @example {@label Validation with custom markdown @id validation-markdown}
* @file {@filename component.ts @filepath ./examples/validation-markdown.component.ts}
* @file {@filename component.ts @filepath ./examples/validation-markdown.component.html}
*
* @example {@label Basic combobox @id combobox}
* @file {@filename component.ts @filepath ./examples/combobox.component.ts}
* @file {@filename component.html @filepath ./examples/combobox.component.html}
*
* @example {@label Combobox with search @id combobox-search}
* @file {@filename component.ts @filepath ./examples/combobox-search.component.ts}
* @file {@filename component.html @filepath ./examples/combobox-search.component.html}
*
* @example {@label Multiple select comboxbox @id combobox-multi}
* @file {@filename component.ts @filepath ./examples/combobox-multi.component.ts}
* @file {@filename component.html @filepath ./examples/combobox-multi.component.html}
*
* @example {@label Multiple select combobox with minwidth @id combobox-multi-minwidth}
* @file {@filename component.ts @filepath ./examples/combobox-multi-minwidth.component.ts}
* @file {@filename component.html @filepath ./examples/combobox-multi-minwidth.component.html}
*
* @example {@label Multiple select combobox with custom tooltip @id custom-tooltip}
* @file {@filename component.ts @filepath ./examples/custom-tooltip.component.ts}
* @file {@filename component.html @filepath ./examples/custom-tooltip.component.html}
*/
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export class FormFieldComponent extends BaseComponent {
/**
* Constructs a new instance of @see FormFieldComponent
* @param injector the angular injection service for the base classes @SmeInjectableBase Annotation.
*/
constructor(injector) {
super(injector);
/**
* Indicates if to maintain the space left after hidding header
*/
this.hideHeaderSpace = true;
/**
* It determines whether to render the label in vertical middle
*/
this.verticalMiddleLabel = false;
this.errorAlertBorder = false;
this.ngModel = injector.get(NgControl, null, { optional: true });
this.accessor = injector.get(FormFieldAccessorDirective);
this.validator = injector.get(FormFieldValidatorDirective);
this.asyncValidator = injector.get(FormFieldAsyncValidatorDirective, null, { optional: true });
this.aggregator = injector.get(SME_FORM_FIELD_AGGREGATOR_PROVIDER, null, { optional: true });
this.layout = injector.get(SME_LAYOUT_PROVIDER, null, { optional: true, skipSelf: true });
this.changeDetectorRef = injector.get(ChangeDetectorRef);
this.required = false;
this.placeholder = '';
this.autofocus = null;
this.initialValue = null;
this.valueInitialized = false;
this.immediateValidation = this.defaultImmediateValidation;
this.ancestorDisabled = false;
this.expectDisabledByAncestor = false;
this.expectDisabledByInput = false;
this.change = new EventEmitter();
this.calculatedOrientation = FormFieldOrientation.Horizontal;
this.previouslyCheckedLayoutWidth = this.layoutWidth;
// Runtime Linting is not necessarily useful for framework controls such as
// form fields since they are designed with accessibility in mind.
this.setAttribute('sme-lint-disable', '');
// validate selector to be in the 'sme-form-field[type="<type>"]' format
if (this.hostElement.nativeElement.tagName.toLowerCase() !== FormFieldComponent.selector) {
Logging.logError('FormField.constructor', `Components derived from ${FormFieldComponent.selector} must use a base selector of ${FormFieldComponent.selector}[type="..."]`);
}
}
/**
* The description of this field; A description for the user of the meaning and purpose of this field
*/
get description() {
return this.internalDescription;
}
set description(value) {
this.internalDescription = value;
this.updateDetails();
}
/**
* The instructions of this control; A built in description for the user of how this control works.
* This should be provided by the control and applies to all instances of the control, but can be overridden as an attribute.
*/
get instructions() {
return this.internalInstructions;
}
set instructions(value) {
this.internalInstructions = value;
this.updateDetails();
}
/**
* Indicates that this form control is truly disabled
* This will be true if the accessor (model), input, or ancestor is disabled
* Its value is set only on DoCheck.
*/
get disabled() {
// return true if we are disabled, our model is disabled or an ancestor is disabled
return this.internalDisabled;
}
/**
* It indicates whether the form field is pending validation
*/
get pending() {
return this.ngModel && this.ngModel.pending;
}
get dirty() {
return this.ngModel && this.ngModel.dirty;
}
get touched() {
return this.ngModel && this.ngModel.touched;
}
get loadingOrDisabled() {
return (this.disabled || this.loading) ? true : null;
}
/**
* The form field's UTA id getter.
* The ID is based on the form field name that's set on the ngModel.
*/
get dataUtaId() {
// First, get the name from the attribute set in the HTML
const formFieldNameAttribute = this.getAttribute('formcontrolname') || this.getAttribute('name');
// Then, if no HTML attribute is set, try to get the name from the ngModel
const formFieldName = formFieldNameAttribute || this.ngModel && this.ngModel.name && this.ngModel.name.toString();
// If none of these are set, then look for the utaId input
const formFieldId = formFieldName || this.utaId;
// Finally, only set the data-uta-id attribute if neither name nor formcontrolname are set on the HTML already
return MsftSme.isNullOrUndefined(formFieldNameAttribute) ? formFieldId : undefined;
}
/**
* Placeholder for the combined disabled state
*/
get internalDisabled() {
return this.ancestorDisabled || this.disabledInput || this.disabledByModel;
}
get fieldName() {
const strings = MsftSme.self().Resources.strings.MsftSmeShell.Angular.Common.Form.Validation;
return this.label || ((this.ngModel && this.ngModel.name) ? this.ngModel.name.toString() : strings.UnknownFieldName);
}
/**
* The value of this field.
* safe wrapper around this.accessor.value
*/
get value() {
return this.accessor.getValue();
}
set value(value) {
this.accessor.writeValue(value, true);
}
get showInfoBubble() {
return (!!this.resolvedTitle || !!this.resolvedTooltipTemplate);
}
get resolvedTooltipTemplate() {
return this.tooltipTemplate;
}
get resolvedTitle() {
return this.tooltip || this.details;
}
/**
* Indicates the default value for immediateValidation.
* This is meant to be overridden by derived classes
*/
get defaultImmediateValidation() {
return false;
}
/**
* Indicates if the form field should layout horizontally
*/
get isHorizontalLayout() {
const orientation = FormFieldOrientation.fromBindableType(this.orientation) ||
FormFieldOrientation.fromBindableType(this.calculatedOrientation);
return orientation === FormFieldOrientation.Horizontal;
}
/**
* Indicates if the form field should layout vertically
*/
get isVerticalLayout() {
const orientation = FormFieldOrientation.fromBindableType(this.orientation) ||
FormFieldOrientation.fromBindableType(this.calculatedOrientation);
return orientation === FormFieldOrientation.Vertical;
}
/**
* Getter for the available width for subfields to consume
*/
get subFieldLayoutWidth() {
const element = this.hostElement.nativeElement;
return element.clientWidth;
}
/**
* Gets the width to base layout decisions on
*/
get layoutWidth() {
const element = this.hostElement.nativeElement;
if (!element) {
return 0;
}
return element.offsetParent ? element.offsetParent.clientWidth : element.clientWidth;
}
/**
* Implementation of angular OnInit interface
*/
ngOnInit() {
super.ngOnInit();
if (!this.type) {
throw new Error(`The "type" attribute is required on all ${FormFieldComponent.selector} components`);
}
if (!this.ngModel) {
// eslint-disable-next-line max-len
throw new Error(`${FormFieldComponent.selector} components require the use of an NgControl directive such as [ngModel], [formControlName], or [formControl]`);
}
if (this.formFieldContainer) {
this.formFieldContainer.checkValidation = () => {
this.ngModel.control.markAsDirty();
this.ngModel.control.updateValueAndValidity();
return this.ngModel.valid;
};
this.formFieldContainer.immediateValidation = () => {
if (this.immediateValidation) {
this.ngModel.control.markAsDirty();
this.ngModel.control.updateValueAndValidity();
this.ngModel.control.markAsPristine();
}
return this.ngModel.valid;
};
this.formFieldContainer.isDirty = () => {
return this.ngModel.dirty;
};
}
// hook up layout directive to get layout width
if (this.layout) {
this.subscriptions.push(this.layout.layoutChanged.subscribe(() => { this.yieldOnLayoutChanged(); }));
}
// subscribe to our validators customValidate event.
this.subscriptions.push(this.validator.customValidate.subscribe(
// Merge our alerts with the alerts object given in the event args
(args) => { MsftSme.deepAssign(args.alerts, this.validate(args.formControl)); }));
// subscribe to our validators alert event.
this.subscriptions.push(this.validator.alert.subscribe((alerts) => { this.onAlert(alerts); }));
if (this.asyncValidator && !this.asyncValidator.customAsyncValidateDisabled) {
// subscribe to our asyncValidators customAsyncValidate event.
this.subscriptions.push(this.asyncValidator.customAsyncValidate.subscribe(
// Merge our alerts with the alerts object given in the event args
(args) => { args.alerts.push(this.asyncValidate(args.formControl)); }));
// subscribe to our asyncValidators asyncAlert event.
this.subscriptions.push(this.asyncValidator.asyncAlert.subscribe((alerts) => { this.onAsyncAlert(alerts); }));
}
// listen to disabled state changes from our model
this.subscriptions.push(this.accessor.disabledChange.subscribe((disabled => this.ngModelDisabledChanged(disabled))));
// subscribe to ngModel status changes.
this.subscriptions.push(this.ngModel.statusChanges.subscribe(status => this.onStatusChanged(status)));
this.subscriptions.push(this.ngModel.valueChanges.subscribe((value) => this.onValueChanged(value)));
// if we have a parent aggregator, then register to it
if (this.aggregator) {
this.aggregator.addFormField(this);
}
// our model may have started in the disabled state, so initialize our tracking property to the current accessor state.
this.disabledByModel = this.accessor.disabled;
// ensure our model starts in a pristine state
this.ngModel.control.markAsPristine();
// a bit hacky, but this works to trigger validation immediately when the form loads.
// this method does not work properly with editable grids using formFieldContainer, use explicit immediateValidation call above.
if (!this.formFieldContainer && this.immediateValidation) {
// This triggers immediate validation
this.ngModel.control.markAsDirty();
// This reverts the pristine state of the form field but it needs to "wait a little"
setTimeout(() => {
this.ngModel.control.markAsPristine();
}, 250);
}
}
/**
* Implementation of angular DoCheck interface
*/
ngDoCheck() {
super.ngDoCheck();
// Check to see if we need to recalculate our layout
const layoutWidth = this.layoutWidth;
if (layoutWidth !== this.previouslyCheckedLayoutWidth) {
this.onLayoutChanged();
}
this.previouslyCheckedLayoutWidth = layoutWidth;
}
/**
* synchronizes the expected disabled state with the actual state of the model
*/
synchronizeDisabledState() {
const disable = this.internalDisabled;
if (disable === this.accessor.disabled) {
return;
}
if (disable) {
if (this.ancestorDisabled) {
this.expectDisabledByAncestor = true;
}
if (this.disabledInput) {
this.expectDisabledByInput = true;
}
this.ngModel.control.disable();
}
else if (!this.disabledByModel) {
this.expectDisabledByAncestor = false;
this.expectDisabledByInput = false;
this.ngModel.control.enable();
}
}
ngOnChanges(changes) {
super.ngOnChanges(changes);
if (changes.loading) {
this.updateAlerts();
}
if (changes.disabledInput) {
setTimeout(() => this.synchronizeDisabledState());
}
}
ngAfterViewInit() {
super.ngAfterViewInit();
// initialize fieldset ancestors. Our ancestry should never change, so we can save perf by collecting this now.
const fieldsetAncestors = Dom.getAllAncestors(this.hostElement.nativeElement, (element) => element.tagName.toUpperCase() === 'FIELDSET');
// listen to the custom disabledChanged event on our ancestor fieldsets
const disabledChangedObservables = fieldsetAncestors.map(f => fromEvent(f, DisabledChangedEvent.typeName));
this.subscriptions.push(merge(...disabledChangedObservables).subscribe(() => {
this.ancestorDisabled = fieldsetAncestors.some(f => f.disabled);
this.synchronizeDisabledState();
}));
// initialize our ancestral disabled state
this.ancestorDisabled = fieldsetAncestors.some(f => f.disabled);
this.synchronizeDisabledState();
}
/**
* Implementation of angular OnDestroy interface
* derived classes are always expected to call super.ngOnDestroy() when overriding
*/
ngOnDestroy() {
super.ngOnDestroy();
// if we have a parent aggregator, then register to it
if (this.aggregator) {
this.aggregator.removeFormField(this);
}
}
/**
* On layout changed event handler, occurs every time the layout has been changed.
*/
onLayoutChanged() {
const layoutWidth = this.layoutWidth;
// 600px is breakpoint for form field vertical/horizontal layout
const orientation = layoutWidth >= 600 ? FormFieldOrientation.Horizontal : FormFieldOrientation.Vertical;
if (this.calculatedOrientation !== orientation) {
this.calculatedOrientation = orientation;
this.changeDetectorRef.detectChanges();
this.yieldOnOrientationChanged();
}
}
/**
* Occurs every time the orientation has been changed.
*/
onOrientationChanged() {
// To be implemented optionally by derived components.
}
/**
* Occurs every time the layout has been changed.
*/
yieldOnLayoutChanged() {
if (!this.ngIsDestroyed) {
this.onLayoutChanged();
}
}
/**
* Occurs every time the layout has been changed.
*/
yieldOnOrientationChanged() {
if (!this.ngIsDestroyed) {
this.onLayoutChanged();
}
}
/**
* Resets this field to its initial value
*/
reset() {
this.ngModel.reset();
}
/**
* Clears the value of this field base on the initial values type.
*/
clear() {
this.value = this.getClearValue();
}
/**
* Copies the fields value to the clipboard.
*/
copyToClipboard() {
MsftSme.copyToClipboard(this.value);
}
/**
* Check whether the header should be displayed.
*/
shouldDisplayHeader() {
return !this.hideHeader && (!!(this.label || this.details) || this.required);
}
/**
* Gets a value indicating if this form field is valid
*/
isValid() {
return this.ngModel.valid;
}
/**
* Gets a value indicating if the form field is dirty.
*/
isDirty() {
return this.ngModel.dirty;
}
/**
* Marks the form field as pristine.
*/
markAsPristine() {
this.ngModel.control.markAsPristine();
}
/**
* Marks the form field as dirty.
*/
markAsDirty() {
this.ngModel.control.markAsDirty();
}
/**
* Applies the focus to the current element
*/
focus() {
const next = Dom.getNextFocusableElement(this.hostElement.nativeElement);
if (next) {
next.focus();
}
}
/**
* Occurs every time the control is disabled or enabled.
* @param disabled The new disabled state of the control
*/
ngModelDisabledChanged(disabled) {
if (disabled) {
const expectDisabled = this.expectDisabledByAncestor || this.expectDisabledByInput;
if (this.expectDisabledByAncestor) {
this.expectDisabledByAncestor = false;
}
if (this.expectDisabledByInput) {
this.expectDisabledByInput = false;
}
if (!expectDisabled) {
this.disabledByModel = true;
this.synchronizeDisabledState();
}
}
else {
this.disabledByModel = false;
this.synchronizeDisabledState();
}
}
/**
* Determines the value to use when clearing the field based on the initial value type
* Derived fields may override this to change the clear value behavior
*/
getClearValue() {
if (MsftSme.isNullOrUndefined(this.initialValue)) {
return null;
}
if (Array.isArray(this.initialValue)) {
return [];
}
switch (typeof this.initialValue) {
case 'string': {
return '';
}
case 'boolean': {
return false;
}
default: {
return this.initialValue;
}
}
}
/**
* Performs validation that is internal to this control
* @param c The form control attached to this instance
*/
validate(c) {
// To be implemented optionally by derived components.
return {};
}
/**
* Performs asynchronous validation that is internal to this control
* @param c The form control attached to this instance
*/
asyncValidate(c) {
// To be implemented optionally by derived components.
return of({});
}
/**
* Occurs any time value changed.
*/
updateDetails() {
const details = [];
if (!MsftSme.isNullOrWhiteSpace(this.description)) {
details.push(this.description);
}
if (!MsftSme.isNullOrWhiteSpace(this.instructions)) {
details.push(this.instructions);
}
// separate details and instructions with double line breaks.
this.details = details.length > 0 ? details.join('\n\n') : null;
}
/**
* Gets the initial host classes to be applied to this element
* When called in the @see BaseComponent super class initialization, These classes will be automatically assigned to the host element.
*/
getInitialHostClasses() {
return super.getInitialHostClasses().concat([
'sme-form-field'
]);
}
/**
* Occurs every time the value of the control changes, in the UI or programmatically.
* @param value the value of the form control
*/
onValueChanged(value) {
if (!this.valueInitialized) {
this.initialValue = value;
this.valueInitialized = true;
}
else {
if (this.formFieldContainer) {
this.formFieldContainer.reportChange();
}
if (this.change.observers.length > 0) {
if (this.ngModel['update'].observers.length > 0) {
setTimeout(() => this.change.emit());
}
else {
this.value = value;
this.change.emit();
}
}
}
}
/**
* Occurs every time the validation status of the control has been re-calculated.
* @param status the status object of the form control
*/
onStatusChanged(status) {
this.updateAlerts();
if (this.aggregator) {
this.aggregator.updateStatus();
}
}
/**
* Occurs every time there are alert changes from the validator directive
* @param alerts the alerts from the validator directive
*/
onAlert(alerts) {
this.nonErrorAlerts = this.removeErrorAlerts(alerts);
this.updateAlerts();
}
/**
* Occurs every time there are alert changes from the validator directive
* @param alerts the alerts from the validator directive
*/
onAsyncAlert(alerts) {
this.nonErrorAsyncAlerts = this.removeErrorAlerts(alerts);
this.updateAlerts();
}
/**
* Removes the error alerts from a ValidationAlert Objects
* errors will be surfaced in the this.ngModel.control.errors instead
* @param alerts the alerts to filter
*/
removeErrorAlerts(alerts) {
const nonErrorAlerts = {};
Object.keys(alerts).forEach(key => {
const alert = alerts[key];
if (alert && alert.valid) {
nonErrorAlerts[key] = alert;
}
});
return nonErrorAlerts;
}
/**
* Updates the form field border to reflect error alerts
*/
updateBorderState() {
if (this.alert && this.alert.severity === ValidationAlertSeverity.Error
&& (this.alert.valid || this.alert.showWhenPristine || this.ngModel.dirty)) {
this.errorAlertBorder = true;
}
else {
this.errorAlertBorder = false;
}
}
/**
* Updates the active alerts of this control
*/
updateAlerts() {
const strings = MsftSme.self().Resources.strings;
this.alert = null;
// When in a loading state, show that alert only.
if (this.loading) {
this.alert = {
pending: true,
message: this.loadingMessage || strings.MsftSmeShell.Angular.Common.Form.Loading.message
};
return;
}
// When in a pending state, show that alert only.
if (this.pending) {
this.alert = {
pending: true,
message: this.pendingMessage
|| strings.MsftSmeShell.Angular.Common.Form.Validation.Pending.message
};
return;
}
// gather our alerts.
const alertsArray = [];
this.addVisibleAlerts(alertsArray, this.nonErrorAlerts);
this.addVisibleAlerts(alertsArray, this.nonErrorAsyncAlerts);
this.addVisibleAlerts(alertsArray, this.ngModel.control.errors);
// clear active alert if there are no alerts
if (alertsArray.length === 0) {
if (this.formFieldContainer) {
this.formFieldContainer.updateAlert(null);
}
this.updateBorderState();
return;
}
const formFieldErrorNames = { required: 'required', invalidDisplayValue: 'invalidDisplayValue' };
// Pre sort for invalid display errors to come before required errors, they have the same severity
alertsArray.sort((a, b) => {
if (a.name === formFieldErrorNames.required && b.name === formFieldErrorNames.invalidDisplayValue) {
return 1;
}
if (a.name === formFieldErrorNames.invalidDisplayValue && b.name === formFieldErrorNames.required) {
return -1;
}
return 0;
});
const sortedAlerts = alertsArray
.map(e => this.getAlert(e.name, e.value))
.sort((a, b) => {
// first sort on valid property
if (a.valid !== b.valid) {
return a.valid ? 1 : -1;
}
// then sort on severity
if (a.severity !== b.severity) {
return a.severity < b.severity ? 1 : -1;
}
// otherwise they are considered equal for sorting
return 0;
});
sortedAlerts.some(alert => {
// Show alert in these scenarios:
// - If the form field is required and it is not the initial load of the form
// - If a custom alert marks showWhenPrisitine as true
// - If the alert is marked as valid
// - Any other alerts after the form is marked as dirty
if (alert.severity !== ValidationAlertSeverity.Error || alert.valid || alert.showWhenPristine || this.ngModel.dirty) {
this.alert = alert;
if (this.formFieldContainer) {
this.formFieldContainer.updateAlert(alert);
}
return true;
}
return false;
});
this.updateBorderState();
}
addVisibleAlerts(target, alerts) {
if (!MsftSme.isNullOrUndefined(alerts)) {
MsftSme.forEachKey(alerts, (k, v) => {
if (!v.hidden) {
target.push({ name: k, value: v });
}
});
}
}
/**
* gets an alert object from a key and validation result
*/
getAlert(key, alert) {
// if the alert is false, null, or valid with no message, then its not really an alert that needs to be shown and should be ignored
if (alert === false
|| MsftSme.isNullOrUndefined(alert)
|| (alert.valid === true) && MsftSme.isNullOrWhiteSpace(alert.message)) {
return null;
}
let processedAlert;
// if the alert is a string, show it as an error
if (typeof alert === 'string') {
processedAlert = {
valid: false,
severity: ValidationAlertSeverity.Error,
message: alert
};
}
else {
processedAlert = this.getKnownNgAlert(key, alert);
if (!processedAlert) {
// if alert is a simple true, we dont really know how to deal with it, so just create an empty validation object
processedAlert = alert === true ? {} : { ...alert };
// if valid is not set, then set it explicitly to false
if (MsftSme.isNullOrUndefined(processedAlert.valid)) {
processedAlert.valid = false;
}
// if there is no message, create a default one
if (MsftSme.isNullOrWhiteSpace(processedAlert.message)) {
processedAlert.message = `Validation failed: ${key}`;
}
// if severity is not set correctly and the field is not valid, then set it to error
if (!processedAlert.valid && MsftSme.isNullOrUndefined(ValidationAlertSeverity[processedAlert.severity])) {
processedAlert.severity = ValidationAlertSeverity.Error;
}
}
}
return processedAlert;
}
/**
* Gets an alert object from known angular alert keys
*/
getKnownNgAlert(key, alert) {
const vStrings = MsftSme.self().Resources.strings.MsftSmeShell.Angular.Common.Form.Validation;
let message;
// first check for known angular 'built-in' validations
switch (key) {
case 'min': {
message = vStrings.Min.format.format(this.fieldName, alert.min);
break;
}
case 'max': {
message = vStrings.Max.format.format(this.fieldName, alert.max);
break;
}
case 'required': {
message = vStrings.Required.format.format(this.fieldName);
break;
}
case 'email': {
message = vStrings.Email.format.format(this.fieldName);
break;
}
case 'minlength': {
message = vStrings.MinLength.format.format(this.fieldName, alert.requiredLength);
break;
}
case 'maxlength': {
message = vStrings.MaxLength.format.format(this.fieldName, alert.requiredLength);
break;
}
case 'pattern': {
message = vStrings.Pattern.format.format(this.fieldName, alert.requiredPattern);
break;
}
default: {
break;
}
}
if (MsftSme.isNullOrWhiteSpace(message)) {
return null;
}
let valid = false;
let severity = ValidationAlertSeverity.Error;
if (!MsftSme.isNullOrWhiteSpace(alert)) {
// merge alert properties if they exist
valid = !!alert.valid;
severity = MsftSme.isNullOrUndefined(alert.severity) ? severity : alert.severity;
message = MsftSme.isNullOrUndefined(alert.message) ? message : alert.message;
}
return {
valid: valid,
severity: severity,
message: message
};
}
/**
* In some cases, we need a combined version of the label and description so that the screen read can read it appropriately.
* Common cases for this are when using role="group" or role="radioGroup" because they does not support aria-describedby
* we achieve the same effect by splitting the description and and label with a '.' (period)"
* TODO: verify how this localizes or find a better way.
*/
getMergedDescriptionLabel() {
if (MsftSme.isNullOrWhiteSpace(this.description)) {
return this.label || '';
}
if (MsftSme.isNullOrWhiteSpace(this.label)) {
return this.description || '';
}
return `${this.label}. ${this.description}`;
}
}
/**
* The selector that all form fields must use in order to work properly with
* @see FormFieldAccessorDirective and @see FormFieldValidatorDirective
*/
FormFieldComponent.selector = 'sme-form-field';
/** @nocollapse */ FormFieldComponent.ɵfac = function FormFieldComponent_Factory(t) { return new (t || FormFieldComponent)(i0.ɵɵdirectiveInject(i0.Injector)); };
/** @nocollapse */ FormFieldComponent.ɵdir = /** @pureOrBreakMyCode */ i0.ɵɵdefineDirective({ type: FormFieldComponent, contentQueries: function FormFieldComponent_ContentQueries(rf, ctx, dirIndex) { if (rf & 1) {
i0.ɵɵcontentQuery(dirIndex, TemplateRef, 5);
} if (rf & 2) {
let _t;
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.subFormTemplate = _t.first);
} }, hostVars: 15, hostBindings: function FormFieldComponent_HostBindings(rf, ctx) { if (rf & 2) {
i0.ɵɵattribute("disabled", ctx.loadingOrDisabled)("aria-disabled", ctx.loadingOrDisabled)("data-uta-id", ctx.dataUtaId);
i0.ɵɵclassProp("sme-form-field-auto-height", ctx.compact)("sme-form-field-minimal", ctx.minimal)("sme-disabled", ctx.loadingOrDisabled)("sme-form-field-error-alert-border", ctx.errorAlertBorder)("sme-form-field-orientation-horizontal", ctx.isHorizontalLayout)("sme-form-field-orientation-vertical", ctx.isVerticalLayout);
} }, inputs: { type: "type", hideHeader: "hideHeader", hideHeaderSpace: "hideHeaderSpace", required: "required", autofocus: "autofocus", placeholder: "placeholder", label: "label", verticalMiddleLabel: "verticalMiddleLabel", description: "description", compact: "compact", minimal: "minimal", readonly: "readonly", instructions: "instructions", tooltipTemplate: "tooltipTemplate", tooltip: "tooltip", tooltipContext: "tooltipContext", disabledInput: ["disabled", "disabledInput"], loading: "loading", loadingMessage: "loadingMessage", pendingMessage: "pendingMessage", utaId: "utaId", immediateValidation: "immediateValidation", orientation: "orientation", formFieldContainer: "formFieldContainer" }, outputs: { change: "change" }, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature] });
__decorate([
Yield(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], FormFieldComponent.prototype, "yieldOnLayoutChanged", null);
__decorate([
Yield(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], FormFieldComponent.prototype, "yieldOnOrientationChanged", null);
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FormFieldComponent, [{
type: Directive
}], function () { return [{ type: i0.Injector }]; }, { type: [{
type: Input
}], hideHeader: [{
type: Input
}], hideHeaderSpace: [{
type: Input
}], required: [{
type: Input
}], autofocus: [{
type: Input
}], placeholder: [{
type: Input
}], label: [{
type: Input
}], verticalMiddleLabel: [{
type: Input
}], subFormTemplate: [{
type: ContentChild,
args: [TemplateRef]
}], description: [{
type: Input
}], compact: [{
type: Input
}, {
type: HostBinding,
args: ['class.sme-form-field-auto-height']
}], minimal: [{
type: Input
}, {
type: HostBinding,
args: ['class.sme-form-field-minimal']
}], readonly: [{
type: Input
}], instructions: [{
type: Input
}], tooltipTemplate: [{
type: Input
}], tooltip: [{
type: Input
}], tooltipContext: [{
type: Input
}], loadingOrDisabled: [{
type: HostBinding,
args: ['class.sme-disabled']
}, {
type: HostBinding,
args: ['attr.disabled']
}, {
type: HostBinding,
args: ['attr.aria-disabled']
}], disabledInput: [{
type: Input,
args: ['disabled']
}], loading: [{
type: Input
}], loadingMessage: [{
type: Input
}], pendingMessage: [{
type: Input
}], utaId: [{
type: Input
}], dataUtaId: [{
type: HostBinding,
args: ['attr.data-uta-id']
}], immediateValidation: [{
type: Input
}], change: [{
type: Output
}], errorAlertBorder: [{
type: HostBinding,
args: ['class.sme-form-field-error-alert-border']
}], isHorizontalLayout: [{
type: HostBinding,
args: ['class.sme-form-field-orientation-horizontal']
}], isVerticalLayout: [{
type: HostBinding,
args: ['class.sme-form-field-orientation-vertical']
}], orientation: [{
type: Input
}], formFieldContainer: [{
type: Input
}], yieldOnLayoutChanged: [], yieldOnOrientationChanged: [] }); })();
/**
* Internal base component for SME for fields. It simply removes the need to supply the string type parameter to FormFieldComponent
* This class is exported from this file, but not meant to be exported from index.ts bundles.
*/
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export class SmeInternalFormFieldComponent extends FormFieldComponent {
}
/** @nocollapse */ SmeInternalFormFieldComponent.ɵfac = /** @pureOrBreakMyCode */ function () { let ɵSmeInternalFormFieldComponent_BaseFactory; return function SmeInternalFormFieldComponent_Factory(t) { return (ɵSmeInternalFormFieldComponent_BaseFactory || (ɵSmeInternalFormFieldComponent_BaseFactory = i0.ɵɵgetInheritedFactory(SmeInternalFormFieldComponent)))(t || SmeInternalFormFieldComponent); }; }();
/** @nocollapse */ SmeInternalFormFieldComponent.ɵdir = /** @pureOrBreakMyCode */ i0.ɵɵdefineDirective({ type: SmeInternalFormFieldComponent, features: [i0.ɵɵInheritDefinitionFeature] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SmeInternalFormFieldComponent, [{
type: Directive
}], null, null); })();
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ybS1maWVsZC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9hbmd1bGFyL3NyYy9jb250cm9scy9mb3JtL2Zvcm0tZmllbGQvZm9ybS1maWVsZC5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sRUFDWSxpQkFBaUIsRUFBRSxZQUFZLEVBQUUsU0FBUyxFQUN6RCxZQUFZLEVBQUUsV0FBVyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQWdDLE1BQU0sRUFBaUIsV0FBVyxFQUMvRyxNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQWUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEQsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLDBFQUEwRSxDQUFDO0FBQ2pHLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSw4REFBOEQsQ0FBQztBQUN2RixPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sa0RBQWtELENBQUM7QUFDdkUsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQWMsRUFBRSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRXhELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSwyQ0FBMkMsQ0FBQztBQUNqRixPQUFPLEVBQVUsbUJBQW1CLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUNsRSxPQUFPLEVBQXFDLHVCQUF1QixFQUFvQixNQUFNLHNDQUFzQyxDQUFDO0FBQ3BJLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzdFLE9BQU8sRUFBOEMsa0NBQWtDLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN6SCxPQUFPLEVBQWlDLGdDQUFnQyxFQUFFLE1BQU0sd0NBQXdDLENBQUM7QUFFekgsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMEJB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