diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index 1ad31796d3..17b07141e1 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -430,6 +430,17 @@ declare module 'azdata' { enabled?: boolean; } + export interface DropDownProperties { + /** + * Adds a short hint that describes the expected value for the editable dropdown + */ + placeholder?: string; + /** + * Define error messages to show when custom validation fails. Note: For empty required dropdowns we use a default error message. + */ + validationErrorMessages?: string[]; + } + /** * Panel component with tabs */ diff --git a/src/sql/base/parts/editableDropdown/browser/dropdown.ts b/src/sql/base/parts/editableDropdown/browser/dropdown.ts index 2d2c3f52fb..e6eb1439ac 100644 --- a/src/sql/base/parts/editableDropdown/browser/dropdown.ts +++ b/src/sql/base/parts/editableDropdown/browser/dropdown.ts @@ -344,8 +344,8 @@ export class Dropdown extends Disposable implements IListVirtualDelegate } public set value(val: string) { + this._input.value = val; if (this._previousValue !== val) { - this._input.value = val; this._previousValue = val; this._onValueChange.fire(val); } diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index 8fca309af5..706d2fd395 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -1550,6 +1550,22 @@ class DropDownWrapper extends ComponentWrapper implements azdata.DropDownCompone let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } + + public get placeholder(): string | undefined { + return this.properties['placeholder']; + } + + public set placeholder(v: string) { + this.setProperty('placeholder', v); + } + + public get validationErrorMessages(): string[] | undefined { + return this.properties['validationErrorMessages']; + } + + public set validationErrorMessages(v: string[]) { + this.setProperty('validationErrorMessages', v); + } } class DeclarativeTableWrapper extends ComponentWrapper implements azdata.DeclarativeTableComponent { diff --git a/src/sql/workbench/browser/modelComponents/dropdown.component.ts b/src/sql/workbench/browser/modelComponents/dropdown.component.ts index 6c13fa5dc6..676c3f72f1 100644 --- a/src/sql/workbench/browser/modelComponents/dropdown.component.ts +++ b/src/sql/workbench/browser/modelComponents/dropdown.component.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/dropdown'; import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ViewChild, ElementRef, OnDestroy, AfterViewInit @@ -23,6 +24,8 @@ import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } fro import { localize } from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { errorForeground, inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry'; @Component({ selector: 'modelview-dropdown', @@ -35,6 +38,16 @@ import { ILogService } from 'vs/platform/log/common/log';
+ ` }) @@ -45,6 +58,10 @@ export default class DropDownComponent extends ComponentBase !this.required || !this.editable || !!this._editableDropdown.value); } @@ -110,6 +128,7 @@ export default class DropDownComponent extends ComponentBase !this.required || this.editable || !!this._selectBox.value); } @@ -121,6 +140,27 @@ export default class DropDownComponent extends ComponentBase { + const validationResult = await super.validate(); + this._changeRef.detectChanges(); + + const element = this.editable ? this._editableDropdown.input.inputElement : this._selectBox.selectElem; + const styleElement = this.editable ? this._editableDropdown.input.element : element; // In case of editable dropdown the border and focus styling comes from the parent element 2 levels up + if (!validationResult) { + element.setAttribute('aria-describedby', this.errorId); + element.setAttribute('aria-errormessage', this.errorId); + element.setAttribute('aria-invalid', 'true'); + styleElement.classList.add('error-dropdown'); + } else { + element.removeAttribute('aria-describedby'); + element.removeAttribute('aria-errormessage'); + element.removeAttribute('aria-invalid'); + styleElement.classList.remove('error-dropdown'); + } + + return validationResult; + } + /// IComponent implementation public setLayout(layout: any): void { @@ -146,6 +186,10 @@ export default class DropDownComponent extends ComponentBase((props) => props.placeholder, undefined); + } + + public get validationErrorMessages(): string[] | undefined { + let validationErrorMessages = this.getPropertyOrDefault((props) => props.validationErrorMessages, undefined); + // Showing the default error message only when user has set a validation error message for the dropdown. + if (this.required && this.editable && validationErrorMessages && (!this._editableDropdown.input.value || this._editableDropdown.input.value === '')) { + return [localize('defaultDropdownErrorMessage', "Please fill out this field.")]; // Adding a default error message for required editable dropdowns having an empty value. + } + return validationErrorMessages; + } + + public get errorId(): string { + return this.descriptor.id + '-err'; + } } + +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + const errorForegroundColor = theme.getColor(errorForeground); + if (errorForegroundColor) { + collector.addRule(` + modelview-dropdown .dropdown-error-text { + color: ${errorForegroundColor}; + } + `); + } + const inputValidationErrorBorderColor = theme.getColor(inputValidationErrorBorder); + if (inputValidationErrorBorderColor) { + collector.addRule(` + modelview-dropdown .error-dropdown { + border-color: ${inputValidationErrorBorderColor} !important; + outline-offset: 2px !important + } + `); + } +}); diff --git a/src/sql/workbench/browser/modelComponents/media/dropdown.css b/src/sql/workbench/browser/modelComponents/media/dropdown.css new file mode 100644 index 0000000000..548df1aae6 --- /dev/null +++ b/src/sql/workbench/browser/modelComponents/media/dropdown.css @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +modelview-dropdown .dropdown-error-container { + width:100%; + display: table; + margin-top: 4px; + position: relative; +} + +modelview-dropdown .dropdown-error-text { + display: block; + margin-left: 22px; +} + +modelview-dropdown .dropdown-error-icon { + height: 14px; + width: 14px; + position: absolute; + top: 2px; + left: 0; +}