diff --git a/src/sql/base/browser/ui/inputBox/inputBox.ts b/src/sql/base/browser/ui/inputBox/inputBox.ts index dd139a151e..64b09e00fa 100644 --- a/src/sql/base/browser/ui/inputBox/inputBox.ts +++ b/src/sql/base/browser/ui/inputBox/inputBox.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; +import { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles, IMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { Color } from 'vs/base/common/color'; import { Event, Emitter } from 'vs/base/common/event'; @@ -33,6 +33,7 @@ export class InputBox extends vsInputBox { public onLoseFocus: Event = this._onLoseFocus.event; private _isTextAreaInput: boolean; + private _hideErrors = false; constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options?: IInputOptions) { super(container, contextViewProvider, options); @@ -103,4 +104,21 @@ export class InputBox extends vsInputBox { public isEnabled(): boolean { return !this.inputElement.hasAttribute('disabled'); } + + public get hideErrors(): boolean { + return this._hideErrors; + } + + public set hideErrors(hideErrors: boolean) { + this._hideErrors = hideErrors; + if (hideErrors) { + this.hideMessage(); + } + } + + public showMessage(message: IMessage, force?: boolean): void { + if (!this.hideErrors) { + super.showMessage(message, force); + } + } } \ No newline at end of file diff --git a/src/sql/parts/modelComponents/componentBase.ts b/src/sql/parts/modelComponents/componentBase.ts index c25c5377ae..8a11a771bc 100644 --- a/src/sql/parts/modelComponents/componentBase.ts +++ b/src/sql/parts/modelComponents/componentBase.ts @@ -207,7 +207,9 @@ export abstract class ContainerBase extends ComponentBase { ) { super(_changeRef); this.items = []; - this._validations.push(() => this.items.every(item => this.modelStore.getComponent(item.descriptor.id).valid)); + this._validations.push(() => this.items.every(item => { + return this.modelStore.getComponent(item.descriptor.id).valid; + })); } /// IComponent container-related implementation diff --git a/src/sql/parts/modelComponents/inputbox.component.ts b/src/sql/parts/modelComponents/inputbox.component.ts index 4506ecb235..d77438229b 100644 --- a/src/sql/parts/modelComponents/inputbox.component.ts +++ b/src/sql/parts/modelComponents/inputbox.component.ts @@ -18,9 +18,8 @@ import { IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { Event, Emitter } from 'vs/base/common/event'; import * as nls from 'vs/nls'; -import { TextAreaInput } from 'vs/editor/browser/controller/textAreaInput'; +import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry'; @Component({ selector: 'modelview-inputBox', @@ -70,13 +69,13 @@ export default class InputBoxComponent extends ComponentBase implements ICompone if (this._inputContainer) { this._input = new InputBox(this._inputContainer.nativeElement, this.contextViewService, inputOptions); this.registerInput(this._input, () => !this.multiline); - } if (this._textareaContainer) { let textAreaInputOptions = Object.assign({}, inputOptions, { flexibleHeight: true, type: 'textarea' }); this._textAreaInput = new InputBox(this._textareaContainer.nativeElement, this.contextViewService, textAreaInputOptions); this.registerInput(this._textAreaInput, () => this.multiline); } + this.inputElement.hideErrors = true; } private get inputElement(): InputBox { @@ -88,10 +87,17 @@ export default class InputBoxComponent extends ComponentBase implements ICompone this._validations.push(() => !input.inputElement.validationMessage); this._register(input); - this._register(attachInputBoxStyler(input, this.themeService)); - this._register(input.onDidChange(e => { + this._register(attachInputBoxStyler(input, this.themeService, { + inputValidationInfoBackground: inputBackground, + inputValidationInfoBorder: inputBorder, + })); + this._register(input.onDidChange(async e => { if (checkOption()) { this.value = input.value; + await this.validate(); + if (input.hideErrors) { + input.hideErrors = false; + } this._onEventEmitter.fire({ eventType: ComponentEventType.onDidChange, args: e diff --git a/src/sql/parts/modelComponents/viewBase.ts b/src/sql/parts/modelComponents/viewBase.ts index a00349e4f9..196498910c 100644 --- a/src/sql/parts/modelComponents/viewBase.ts +++ b/src/sql/parts/modelComponents/viewBase.ts @@ -12,7 +12,7 @@ import nls = require('vs/nls'); import * as sqlops from 'sqlops'; import { IModelStore, IComponentDescriptor, IComponent, IComponentEventArgs } from './interfaces'; import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { IModelView } from 'sql/services/model/modelViewService'; +import { IModelView, IModelViewEventArgs } from 'sql/services/model/modelViewService'; import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry'; import { AngularDisposable } from 'sql/base/common/lifecycle'; import { ModelStore } from 'sql/parts/modelComponents/modelStore'; @@ -38,7 +38,7 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { abstract id: string; abstract connection: sqlops.connection.Connection; abstract serverInfo: sqlops.ServerInfo; - private _onEventEmitter = new Emitter(); + private _onEventEmitter = new Emitter(); initializeModel(rootComponent: IComponentShape, validationCallback: (componentId: string) => Thenable): void { let descriptor = this.defineComponent(rootComponent); @@ -106,13 +106,16 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { registerEvent(componentId: string) { this.queueAction(componentId, (component) => { this._register(component.registerEventHandler(e => { - e.componentId = componentId; - this._onEventEmitter.fire(e); + let modelViewEvent: IModelViewEventArgs = Object.assign({ + componentId: componentId, + isRootComponent: componentId === this.rootDescriptor.id + }, e); + this._onEventEmitter.fire(modelViewEvent); })); }); } - public get onEvent(): Event { + public get onEvent(): Event { return this._onEventEmitter.event; } diff --git a/src/sql/platform/dialog/dialogContainer.component.ts b/src/sql/platform/dialog/dialogContainer.component.ts index f562346ffd..7e7019c41a 100644 --- a/src/sql/platform/dialog/dialogContainer.component.ts +++ b/src/sql/platform/dialog/dialogContainer.component.ts @@ -56,7 +56,7 @@ export class DialogContainer implements AfterContentInit { ngAfterContentInit(): void { this._modelViewContent.onEvent(event => { - if (event.eventType === ComponentEventType.validityChanged) { + if (event.isRootComponent && event.eventType === ComponentEventType.validityChanged) { this._params.validityChangedCallback(event.args); } }); diff --git a/src/sql/services/model/modelViewService.ts b/src/sql/services/model/modelViewService.ts index d4c21e83e3..3e7b83c78f 100644 --- a/src/sql/services/model/modelViewService.ts +++ b/src/sql/services/model/modelViewService.ts @@ -6,7 +6,8 @@ 'use strict'; import * as sqlops from 'sqlops'; import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { Event, Emitter } from 'vs/base/common/event'; +import { IComponentEventArgs } from 'sql/parts/modelComponents/interfaces'; +import { Event } from 'vs/base/common/event'; export interface IView { readonly id: string; @@ -14,6 +15,10 @@ export interface IView { readonly serverInfo: sqlops.ServerInfo; } +export interface IModelViewEventArgs extends IComponentEventArgs { + isRootComponent: boolean; +} + export interface IModelView extends IView { initializeModel(rootComponent: IComponentShape, validationCallback?: (componentId: string) => Thenable): void; clearContainer(componentId: string): void; @@ -21,7 +26,7 @@ export interface IModelView extends IView { setLayout(componentId: string, layout: any): void; setProperties(componentId: string, properties: { [key: string]: any }): void; registerEvent(componentId: string); - onEvent: Event; + onEvent: Event; validate(componentId: string): Thenable; readonly onDestroy: Event; } diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index f9dd4617dc..b9847ede0b 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -133,6 +133,7 @@ declare module 'sqlops' { component: Component; title: string; actions?: Component[]; + required?: boolean; } export interface ToolbarComponent { @@ -240,7 +241,6 @@ declare module 'sqlops' { componentWidth?: number | string; componentHeight?: number | string; titleFontSize?: number | string; - required?: boolean; info?: string; } diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index e1879abb58..b379355608 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -255,7 +255,7 @@ class FormContainerBuilder extends ContainerBuilderImpl { }); assert.equal(validityFromEvent, false, 'Main thread validityChanged event did not cause component to fire its own event'); }); + + test('Setting a form component as required initializes the model with the component required', () => { + // Set up the input component with required initially set to false + let inputComponent = modelView.modelBuilder.inputBox().component(); + inputComponent.required = false; + + // If I build a form that sets the input component as required + let inputFormComponent: sqlops.FormComponent = { + component: inputComponent, + title: 'test_input', + required: true + }; + let requiredFormContainer = modelView.modelBuilder.formContainer().withFormItems([inputFormComponent]).component(); + modelView.initializeModel(requiredFormContainer); + + // Then the input component is sent to the main thread with required set to true + mockProxy.verify(x => x.$initializeModel(It.isAny(), It.is(rootComponent => { + return rootComponent.itemConfigs.length === 1 && rootComponent.itemConfigs[0].componentShape.id === inputComponent.id && rootComponent.itemConfigs[0].componentShape.properties['required'] === true; + })), Times.once()); + }); }); \ No newline at end of file