diff --git a/src/sql/base/browser/ui/checkbox/checkbox.ts b/src/sql/base/browser/ui/checkbox/checkbox.ts index 101008810c..4403c395ce 100644 --- a/src/sql/base/browser/ui/checkbox/checkbox.ts +++ b/src/sql/base/browser/ui/checkbox/checkbox.ts @@ -12,6 +12,7 @@ export interface ICheckboxOptions { enabled?: boolean; checked?: boolean; onChange?: (val: boolean) => void; + ariaLabel?: string; } export class Checkbox extends Widget { @@ -27,6 +28,10 @@ export class Checkbox extends Widget { this._el = document.createElement('input'); this._el.type = 'checkbox'; + if (opts.ariaLabel) { + this._el.setAttribute('aria-label', opts.ariaLabel); + } + this.onchange(this._el, e => { this._onChange.fire(this.checked); }); diff --git a/src/sql/base/browser/ui/modal/optionsDialogHelper.ts b/src/sql/base/browser/ui/modal/optionsDialogHelper.ts index b9c28d348c..55cb7fb46c 100644 --- a/src/sql/base/browser/ui/modal/optionsDialogHelper.ts +++ b/src/sql/base/browser/ui/modal/optionsDialogHelper.ts @@ -43,7 +43,8 @@ export function createOptionElement(option: sqlops.ServiceOption, rowContainer: return null; } } - } + }, + ariaLabel: option.displayName }); optionWidget.value = optionValue; inputElement = this.findElement(rowContainer, 'input'); @@ -55,7 +56,8 @@ export function createOptionElement(option: sqlops.ServiceOption, rowContainer: optionWidget = new InputBox(rowContainer.getHTMLElement(), contextViewService, { validationOptions: { validation: (value: string) => (!value && option.isRequired) ? ({ type: MessageType.ERROR, content: option.displayName + missingErrorMessage }) : null - } + }, + ariaLabel: option.displayName }); optionWidget.value = optionValue; if (option.valueType === ServiceOptionType.password) { diff --git a/src/sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialog.ts b/src/sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialog.ts index 0d5c2b94a4..df9e33c144 100644 --- a/src/sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialog.ts +++ b/src/sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialog.ts @@ -104,7 +104,9 @@ export class AutoOAuthDialog extends Modal { }); inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { - inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService); + inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, { + ariaLabel: label + }); }); }); return inputBox; diff --git a/src/sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog.ts b/src/sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog.ts index 1486cdac47..608f59a65a 100644 --- a/src/sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog.ts +++ b/src/sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog.ts @@ -34,6 +34,11 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys'; // in case that other non-Azure sign in is to be used const firewallHelpUri = 'https://aka.ms/sqlopsfirewallhelp'; +const LocalizedStrings = { + FROM: localize('from', 'From'), + TO: localize('to', 'To') +}; + export class FirewallRuleDialog extends Modal { public viewModel: FirewallRuleViewModel; private _createButton: Button; @@ -140,19 +145,23 @@ export class FirewallRuleDialog extends Modal { subnetIPRangeSection = subnetIPRangeContainer.getHTMLElement(); subnetIPRangeContainer.div({ 'class': 'dialog-input-section' }, (inputContainer) => { inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => { - labelContainer.innerHtml(localize('from', 'From')); + labelContainer.innerHtml(LocalizedStrings.FROM); }); inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => { - this._fromRangeinputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService); + this._fromRangeinputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, { + ariaLabel: LocalizedStrings.FROM + }); }); inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => { - labelContainer.innerHtml(localize('to', 'To')); + labelContainer.innerHtml(LocalizedStrings.TO); }); inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => { - this._toRangeinputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService); + this._toRangeinputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, { + ariaLabel: LocalizedStrings.TO + }); }); }); }); diff --git a/src/sql/parts/connection/connectionDialog/connectionWidget.ts b/src/sql/parts/connection/connectionDialog/connectionWidget.ts index ad653bcad8..6b05f13f69 100644 --- a/src/sql/parts/connection/connectionDialog/connectionWidget.ts +++ b/src/sql/parts/connection/connectionDialog/connectionWidget.ts @@ -207,7 +207,7 @@ export class ConnectionWidget { container.element('tr', {}, (rowContainer) => { rowContainer.element('td'); rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => { - checkbox = new Checkbox(inputCellContainer.getHTMLElement(), { label, checked: isChecked }); + checkbox = new Checkbox(inputCellContainer.getHTMLElement(), { label, checked: isChecked, ariaLabel: label }); }); }); return checkbox; diff --git a/src/sql/parts/dashboard/widgets/explorer/explorerWidget.component.ts b/src/sql/parts/dashboard/widgets/explorer/explorerWidget.component.ts index 6a3d9e80a2..c9015dc8da 100644 --- a/src/sql/parts/dashboard/widgets/explorer/explorerWidget.component.ts +++ b/src/sql/parts/dashboard/widgets/explorer/explorerWidget.component.ts @@ -62,8 +62,11 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget, ngOnInit() { this._inited = true; + let placeholderLabel = this._config.context === 'database' ? nls.localize('seachObjects', 'Search by name of type (a:, t:, v:, f:, or sp:)') : nls.localize('searchDatabases', 'Search databases'); + let inputOptions: IInputOptions = { - placeholder: this._config.context === 'database' ? nls.localize('seachObjects', 'Search by name of type (a:, t:, v:, f:, or sp:)') : nls.localize('searchDatabases', 'Search databases') + placeholder: placeholderLabel, + ariaLabel: placeholderLabel }; this._input = new InputBox(this._inputContainer.nativeElement, this._bootstrap.contextViewService, inputOptions); this._register(this._input.onDidChange(e => { diff --git a/src/sql/parts/disasterRecovery/backup/backup.component.ts b/src/sql/parts/disasterRecovery/backup/backup.component.ts index e35d978a41..dcdfc82472 100644 --- a/src/sql/parts/disasterRecovery/backup/backup.component.ts +++ b/src/sql/parts/disasterRecovery/backup/backup.component.ts @@ -207,7 +207,10 @@ export class BackupComponent { let self = this; this.addFooterButtons(); - this.recoveryBox = new InputBox(this.recoveryModelElement.nativeElement, this._bootstrapService.contextViewService, { placeholder: this.recoveryModel }); + this.recoveryBox = new InputBox(this.recoveryModelElement.nativeElement, this._bootstrapService.contextViewService, { + placeholder: this.recoveryModel, + ariaLabel: this.recoveryModelLabel + }); // Set backup type this.backupTypeSelectBox = new SelectBox([], '', this._bootstrapService.contextViewService); this.backupTypeSelectBox.render(this.backupTypeElement.nativeElement); @@ -216,39 +219,46 @@ export class BackupComponent { this.copyOnlyCheckBox = new Checkbox(this.copyOnlyElement.nativeElement, { label: this.copyOnlyLabel, checked: false, - onChange: (viaKeyboard) => { } + onChange: (viaKeyboard) => { }, + ariaLabel: this.copyOnlyLabel }); // Encryption checkbox this.encryptCheckBox = new Checkbox(this.encryptElement.nativeElement, { label: this.encryptionLabel, checked: false, - onChange: (viaKeyboard) => self.onChangeEncrypt() + onChange: (viaKeyboard) => self.onChangeEncrypt(), + ariaLabel: this.encryptionLabel }); // Verify backup checkbox this.verifyCheckBox = new Checkbox(this.verifyElement.nativeElement, { label: this.verifyContainerLabel, checked: false, - onChange: (viaKeyboard) => { } + onChange: (viaKeyboard) => { }, + ariaLabel: this.verifyContainerLabel }); // Perform checksum checkbox this.checksumCheckBox = new Checkbox(this.checksumElement.nativeElement, { label: this.checksumContainerLabel, checked: false, - onChange: (viaKeyboard) => { } + onChange: (viaKeyboard) => { }, + ariaLabel: this.checksumContainerLabel }); // Continue on error checkbox this.continueOnErrorCheckBox = new Checkbox(this.continueOnErrorElement.nativeElement, { label: this.continueOnErrorContainerLabel, checked: false, - onChange: (viaKeyboard) => { } + onChange: (viaKeyboard) => { }, + ariaLabel: this.continueOnErrorContainerLabel }); // Set backup name - this.backupNameBox = new InputBox(this.backupNameElement.nativeElement, this._bootstrapService.contextViewService); + this.backupNameBox = new InputBox(this.backupNameElement.nativeElement, this._bootstrapService.contextViewService, { + ariaLabel: this.backupNameLabel + }); // Set backup path list this.pathListBox = new ListBox([], '', this._bootstrapService.contextViewService, this._bootstrapService.clipboardService); @@ -279,10 +289,14 @@ export class BackupComponent { { validationOptions: { validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: this.mediaNameRequiredError }) : null - } - }); + }, + ariaLabel: this.newMediaSetNameLabel + } + ); - this.mediaDescriptionBox = new InputBox(this.mediaDescriptionElement.nativeElement, this._bootstrapService.contextViewService); + this.mediaDescriptionBox = new InputBox(this.mediaDescriptionElement.nativeElement, this._bootstrapService.contextViewService, { + ariaLabel: this.newMediaSetDescriptionLabel + }); // Set backup retain days let invalidInputMessage = localize('backupComponent.invalidInput', 'Invalid input. Value must be greater than or equal 0.'); @@ -300,7 +314,8 @@ export class BackupComponent { return null; } } - } + }, + ariaLabel: this.setBackupRetainDaysLabel }); // Disable elements diff --git a/src/sql/parts/disasterRecovery/restore/restoreDialog.ts b/src/sql/parts/disasterRecovery/restore/restoreDialog.ts index 5b9dfcaf31..1cb80b6f96 100644 --- a/src/sql/parts/disasterRecovery/restore/restoreDialog.ts +++ b/src/sql/parts/disasterRecovery/restore/restoreDialog.ts @@ -40,6 +40,7 @@ import * as DOM from 'vs/base/browser/dom'; import * as sqlops from 'sqlops'; import * as strings from 'vs/base/common/strings'; import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { mixin } from 'vs/base/common/objects'; interface FileListElement { logicalFileName: string; @@ -48,6 +49,11 @@ interface FileListElement { restoreAs: string; } +const LocalizedStrings = { + BACKFILEPATH: localize('backupFilePath', "Backup file path"), + TARGETDATABASE: localize('targetDatabase', 'Target database') +}; + export class RestoreDialog extends Modal { public viewModel: RestoreViewModel; @@ -176,12 +182,13 @@ export class RestoreDialog extends Modal { validationOptions: { validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: errorMessage }) : null }, - placeholder: localize('multipleBackupFilePath', 'Please enter one or more file paths separated by commas') + placeholder: localize('multipleBackupFilePath', 'Please enter one or more file paths separated by commas'), + ariaLabel: LocalizedStrings.BACKFILEPATH }; filePathContainer.div({ class: 'dialog-input-section' }, (inputContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { - labelContainer.innerHtml(localize('backupFilePath', "Backup file path")); + labelContainer.safeInnerHtml(LocalizedStrings.BACKFILEPATH); }); inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { @@ -218,7 +225,7 @@ export class RestoreDialog extends Modal { destinationContainer.div({ class: 'dialog-input-section' }, (inputContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { - labelContainer.innerHtml(localize('targetDatabase', 'Target database')); + labelContainer.innerHtml(LocalizedStrings.TARGETDATABASE); }); inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { @@ -226,7 +233,8 @@ export class RestoreDialog extends Modal { inputCellContainer.style('width', '100%'); this._databaseDropdown = new Dropdown(inputCellContainer.getHTMLElement(), this._contextViewService, this._themeService, { - strictSelection: false + strictSelection: false, + ariaLabel: LocalizedStrings.TARGETDATABASE } ); this._databaseDropdown.onValueChange(s => { @@ -514,7 +522,8 @@ export class RestoreDialog extends Modal { checkbox = new Checkbox(inputCellContainer.getHTMLElement(), { label: label, checked: isChecked, - onChange: onCheck + onChange: onCheck, + ariaLabel: label }); }); return checkbox; @@ -537,13 +546,16 @@ export class RestoreDialog extends Modal { private createInputBoxHelper(container: Builder, label: string, options?: IInputOptions): InputBox { let inputBox: InputBox; + let ariaOptions = { + ariaLabel: label + }; container.div({ class: 'dialog-input-section' }, (inputContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { - labelContainer.innerHtml(label); + labelContainer.safeInnerHtml(label); }); inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { - inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, options); + inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, mixin(ariaOptions, options)); }); }); return inputBox; diff --git a/src/sql/parts/fileBrowser/fileBrowserDialog.ts b/src/sql/parts/fileBrowser/fileBrowserDialog.ts index d20ee276b3..bb647c6edd 100644 --- a/src/sql/parts/fileBrowser/fileBrowserDialog.ts +++ b/src/sql/parts/fileBrowser/fileBrowserDialog.ts @@ -94,7 +94,9 @@ export class FileBrowserDialog extends Modal { tableWrapper.element('table', { class: 'file-table-content' }, (tableContainer) => { let pathLabel = localize('filebrowser.filepath', 'Selected path'); let pathBuilder = DialogHelper.appendRow(tableContainer, pathLabel, 'file-input-label', 'file-input-box'); - this._filePathInputBox = new InputBox(pathBuilder.getHTMLElement(), this._contextViewService); + this._filePathInputBox = new InputBox(pathBuilder.getHTMLElement(), this._contextViewService, { + ariaLabel: pathLabel + }); this._fileFilterSelectBox = new SelectBox(['*'], '*', this._contextViewService); let filterLabel = localize('fileFilter', 'Files of type'); diff --git a/src/sql/parts/grid/views/query/chartViewer.component.ts b/src/sql/parts/grid/views/query/chartViewer.component.ts index c70d2ce1a7..e2a65011f6 100644 --- a/src/sql/parts/grid/views/query/chartViewer.component.ts +++ b/src/sql/parts/grid/views/query/chartViewer.component.ts @@ -118,14 +118,16 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction // Note: must use 'self' for callback this.labelFirstColumnCheckBox = new Checkbox(this.labelFirstColumnElement.nativeElement, { label: this.labelFirstColumnLabel, - onChange: () => this.onLabelFirstColumnChanged() + onChange: () => this.onLabelFirstColumnChanged(), + ariaLabel: this.labelFirstColumnLabel }); // Init label first column checkbox // Note: must use 'self' for callback this.columnsAsLabelsCheckBox = new Checkbox(this.columnsAsLabelsElement.nativeElement, { label: this.columnsAsLabelsLabel, - onChange: () => this.columnsAsLabelsChanged() + onChange: () => this.columnsAsLabelsChanged(), + ariaLabel: this.columnsAsLabelsLabel }); // Init legend dropdown diff --git a/src/sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog.ts b/src/sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog.ts index f6192dd85f..129d85a5d4 100644 --- a/src/sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog.ts +++ b/src/sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog.ts @@ -77,8 +77,8 @@ export class ServerGroupDialog extends Modal { this._bodyBuilder = builder; }); // Connection Group Name + let serverGroupNameLabel = localize('connectionGroupName', 'Server group name'); this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => { - let serverGroupNameLabel = localize('connectionGroupName', 'Server group name'); labelContainer.innerHtml(serverGroupNameLabel); }); this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => { @@ -86,17 +86,20 @@ export class ServerGroupDialog extends Modal { this._groupNameInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, { validationOptions: { validation: (value: string) => !value && !this._skipGroupNameValidation ? ({ type: MessageType.ERROR, content: errorMessage }) : null - } + }, + ariaLabel: serverGroupNameLabel }); }); // Connection Group Description + let groupDescriptionLabel = localize('groupDescription', 'Group description'); this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => { - let groupDescriptionLabel = localize('groupDescription', 'Group description'); labelContainer.innerHtml(groupDescriptionLabel); }); this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => { - this._groupDescriptionInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService); + this._groupDescriptionInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, { + ariaLabel: groupDescriptionLabel + }); }); // Connection Group Color diff --git a/src/sql/parts/objectExplorer/viewlet/connectionViewlet.ts b/src/sql/parts/objectExplorer/viewlet/connectionViewlet.ts index 825e56dcb0..4e44d953f0 100644 --- a/src/sql/parts/objectExplorer/viewlet/connectionViewlet.ts +++ b/src/sql/parts/objectExplorer/viewlet/connectionViewlet.ts @@ -28,6 +28,7 @@ import { warn } from 'sql/base/common/log'; import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { localize } from 'vs/nls'; export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet { @@ -88,12 +89,14 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet { this._viewletContainer = viewletContainer; viewletContainer.div({ class: 'search-box' }, (searchBoxContainer) => { this._searchBoxContainer = searchBoxContainer; + let searchServerString = localize('Search server names', 'Search server names'); this._searchBox = new InputBox( searchBoxContainer.getHTMLElement(), null, { - placeholder: 'Search server names', - actions: [this._clearSearchAction] + placeholder: searchServerString, + actions: [this._clearSearchAction], + ariaLabel: searchServerString } ); this._searchTerm = ''; diff --git a/src/sql/parts/query/execution/queryActions.ts b/src/sql/parts/query/execution/queryActions.ts index 6163fe20f7..ef3bec83ef 100644 --- a/src/sql/parts/query/execution/queryActions.ts +++ b/src/sql/parts/query/execution/queryActions.ts @@ -444,9 +444,11 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem super(); this._toDispose = []; this.$databaseListDropdown = $('.databaseListDropdown'); + let selectString = nls.localize("selectDatabase", "Select Database"); this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, { strictSelection: true, - placeholder: nls.localize("selectDatabase", "Select Database") + placeholder: selectString, + ariaLabel: selectString }); this._dropdown.onValueChange(s => this.databaseSelected(s));