Add validation to model view components (#1356)

This commit is contained in:
Matt Irvine
2018-05-08 14:15:26 -07:00
committed by GitHub
parent c2b32fd64a
commit f10e281ffc
18 changed files with 459 additions and 35 deletions

View File

@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./flexContainer';
import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
ViewChild, ElementRef, Injector, OnDestroy, OnInit
} from '@angular/core';
@@ -18,14 +19,16 @@ import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
export class ItemDescriptor<T> {
constructor(public descriptor: IComponentDescriptor, public config: T) {}
constructor(public descriptor: IComponentDescriptor, public config: T) { }
}
export abstract class ComponentBase extends Disposable implements IComponent, OnDestroy, OnInit {
protected properties: { [key: string]: any; } = {};
constructor (
protected _valid: boolean = true;
private _eventQueue: IComponentEventArgs[] = [];
constructor(
protected _changeRef: ChangeDetectorRef) {
super();
super();
}
/// IComponent implementation
@@ -56,7 +59,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
this.dispose();
}
abstract setLayout (layout: any): void;
abstract setLayout(layout: any): void;
public setProperties(properties: { [key: string]: any; }): void {
if (!properties) {
@@ -77,21 +80,49 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
protected setPropertyFromUI<TPropertyBag, TValue>(propertySetter: (TPropertyBag, TValue) => void, value: TValue) {
propertySetter(this.getProperties<TPropertyBag>(), value);
this._onEventEmitter.fire({
this.fireEvent({
eventType: ComponentEventType.PropertiesChanged,
args: this.getProperties()
});
}
public get onEvent(): Event<IComponentEventArgs> {
return this._onEventEmitter.event;
}
public get title(): string {
let properties = this.getProperties();
let title = properties['title'];
return title ? <string>title : '';
}
public get valid(): boolean {
return this._valid;
}
public setValid(valid: boolean): void {
if (this._valid !== valid) {
this._valid = valid;
this.fireEvent({
eventType: ComponentEventType.validityChanged,
args: valid
});
}
}
public registerEventHandler(handler: (event: IComponentEventArgs) => void): IDisposable {
if (this._eventQueue) {
while (this._eventQueue.length > 0) {
let event = this._eventQueue.pop();
handler(event);
}
this._eventQueue = undefined;
}
return this._onEventEmitter.event(handler);
}
private fireEvent(event: IComponentEventArgs) {
this._onEventEmitter.fire(event);
if (this._eventQueue) {
this._eventQueue.push(event);
}
}
}
export abstract class ContainerBase<T> extends ComponentBase {
@@ -115,5 +146,5 @@ export abstract class ContainerBase<T> extends ComponentBase {
this._changeRef.detectChanges();
}
abstract setLayout (layout: any): void;
abstract setLayout(layout: any): void;
}

View File

@@ -10,10 +10,11 @@ import {
import * as sqlops from 'sqlops';
import Event, { Emitter } from 'vs/base/common/event';
import * as nls from 'vs/nls';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { InputBox, IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
import { InputBox, IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler';
@@ -44,7 +45,19 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
if (this._inputContainer) {
let inputOptions: IInputOptions = {
placeholder: '',
ariaLabel: ''
ariaLabel: '',
validationOptions: {
validation: () => {
if (this.valid) {
return undefined;
} else {
return {
content: nls.localize('invalidValueError', 'Invalid value'),
type: MessageType.ERROR
};
}
}
}
};
this._input = new InputBox(this._inputContainer.nativeElement, this._commonService.contextViewService, inputOptions);
@@ -81,6 +94,11 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
this._input.value = this.value;
}
public setValid(valid: boolean): void {
super.setValid(valid);
this._input.validate();
}
// CSS-bound properties
public get value(): string {

View File

@@ -6,6 +6,7 @@ import { InjectionToken } from '@angular/core';
import * as sqlops from 'sqlops';
import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
/**
* An instance of a model-backed component. This will be a UI element
@@ -17,12 +18,14 @@ export interface IComponent {
descriptor: IComponentDescriptor;
modelStore: IModelStore;
layout();
registerEventHandler(handler: (event: IComponentEventArgs) => void): IDisposable;
clearContainer?: () => void;
addToContainer?: (componentDescriptor: IComponentDescriptor, config: any) => void;
setLayout?: (layout: any) => void;
setProperties?: (properties: { [key: string]: any; }) => void;
readonly valid?: boolean;
setValid(valid: boolean): void;
title?: string;
onEvent?: Event<IComponentEventArgs>;
}
export const COMPONENT_CONFIG = new InjectionToken<IComponentConfig>('component_config');
@@ -60,7 +63,8 @@ export interface IComponentEventArgs {
export enum ComponentEventType {
PropertiesChanged,
onDidChange,
onDidClick
onDidClick,
validityChanged
}
export interface IModelStore {

View File

@@ -12,7 +12,7 @@ import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { Deferred } from 'sql/base/common/promise';
const componentRegistry = <IComponentRegistry> Registry.as(Extensions.ComponentContribution);
const componentRegistry = <IComponentRegistry>Registry.as(Extensions.ComponentContribution);
class ComponentDescriptor implements IComponentDescriptor {

View File

@@ -91,6 +91,10 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
this.queueAction(componentId, (component) => component.setProperties(properties));
}
setValid(componentId: string, valid: boolean): void {
this.queueAction(componentId, (component) => component.setValid(valid));
}
private queueAction<T>(componentId: string, action: (component: IComponent) => T): void {
this.modelStore.eventuallyRunOnComponent(componentId, action).catch(err => {
// TODO add error handling
@@ -99,12 +103,10 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
registerEvent(componentId: string) {
this.queueAction(componentId, (component) => {
if (component.onEvent) {
this._register(component.onEvent(e => {
e.componentId = componentId;
this._onEventEmitter.fire(e);
}));
}
this._register(component.registerEventHandler(e => {
e.componentId = componentId;
this._onEventEmitter.fire(e);
}));
});
}

View File

@@ -11,9 +11,11 @@ import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.com
import { BootstrapParams } from 'sql/services/bootstrap/bootstrapParams';
import { BOOTSTRAP_SERVICE_ID, IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
import Event, { Emitter } from 'vs/base/common/event';
import { ComponentEventType } from '../../parts/modelComponents/interfaces';
export interface DialogComponentParams extends BootstrapParams {
modelViewId: string;
validityChangedCallback: (valid: boolean) => void;
}
@Component({
@@ -27,16 +29,23 @@ export interface DialogComponentParams extends BootstrapParams {
export class DialogContainer implements AfterContentInit {
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
private _params: DialogComponentParams;
public modelViewId: string;
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(BOOTSTRAP_SERVICE_ID) bootstrapService: IBootstrapService) {
this.modelViewId = (bootstrapService.getBootstrapParams(el.nativeElement.tagName) as DialogComponentParams).modelViewId;
this._params = bootstrapService.getBootstrapParams(el.nativeElement.tagName) as DialogComponentParams;
this.modelViewId = this._params.modelViewId;
}
ngAfterContentInit(): void {
this._modelViewContent.onEvent(event => {
if (event.eventType === ComponentEventType.validityChanged) {
this._params.validityChangedCallback(event.args);
}
});
}
public layout(): void {

View File

@@ -21,6 +21,8 @@ import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { Button } from 'vs/base/browser/ui/button/button';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { localize } from 'vs/nls';
import Event, { Emitter } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
export class DialogModal extends Modal {
private _dialogPane: DialogPane;
@@ -102,8 +104,10 @@ export class DialogModal extends Modal {
}
public done(): void {
this.dispose();
this.hide();
if (this._dialog.okButton.enabled) {
this.dispose();
this.hide();
}
}
public cancel(): void {
@@ -119,6 +123,20 @@ export class DialogModal extends Modal {
super.show();
}
/**
* Overridable to change behavior of escape key
*/
protected onClose(e: StandardKeyboardEvent) {
this.cancel();
}
/**
* Overridable to change behavior of enter key
*/
protected onAccept(e: StandardKeyboardEvent) {
this.done();
}
public dispose(): void {
super.dispose();
this._dialogPane.dispose();

View File

@@ -16,12 +16,16 @@ import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.compo
import { Builder } from 'vs/base/browser/builder';
import { IThemable } from 'vs/platform/theme/common/styler';
import { Disposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
export class DialogPane extends Disposable implements IThemable {
private _activeTabIndex: number;
private _tabbedPanel: TabbedPanel;
private _moduleRef: NgModuleRef<{}>;
// Validation
private _modelViewValidityMap = new Map<string, boolean>();
// HTML Elements
private _body: HTMLElement;
private _tabBar: HTMLElement;
@@ -46,12 +50,20 @@ export class DialogPane extends Disposable implements IThemable {
} else {
this._tabbedPanel = new TabbedPanel(this._body);
this._dialog.content.forEach((tab, tabIndex) => {
let tabContainer = document.createElement('div');
tabContainer.style.display = 'none';
this._body.appendChild(tabContainer);
this.initializeModelViewContainer(tabContainer, tab.content);
this._tabbedPanel.pushTab({
title: tab.title,
identifier: 'dialogPane.' + this._dialog.title + '.' + tabIndex,
view: {
render: (container) => {
this.initializeModelViewContainer(container, tab.content);
if (tabContainer.parentElement === this._body) {
this._body.removeChild(tabContainer);
}
container.appendChild(tabContainer);
tabContainer.style.display = 'block';
},
layout: (dimension) => { }
} as IPanelView
@@ -72,7 +84,10 @@ export class DialogPane extends Disposable implements IThemable {
DialogModule,
bodyContainer,
'dialog-modelview-container',
{ modelViewId: modelViewId } as DialogComponentParams,
{
modelViewId: modelViewId,
validityChangedCallback: (valid: boolean) => this._setValidity(modelViewId, valid)
} as DialogComponentParams,
undefined,
(moduleRef) => this._moduleRef = moduleRef);
}
@@ -93,6 +108,21 @@ export class DialogPane extends Disposable implements IThemable {
this._body.style.color = styles.dialogForeground ? styles.dialogForeground.toString() : undefined;
}
private _setValidity(modelViewId: string, valid: boolean) {
let oldValidity = this.isValid();
this._modelViewValidityMap.set(modelViewId, valid);
let newValidity = this.isValid();
if (newValidity !== oldValidity) {
this._dialog.notifyValidityChanged(newValidity);
}
}
private isValid(): boolean {
let valid = true;
this._modelViewValidityMap.forEach(value => valid = valid && value);
return valid;
}
public dispose() {
super.dispose();
this._moduleRef.destroy();

View File

@@ -28,11 +28,24 @@ export class Dialog implements sqlops.window.modelviewdialog.Dialog {
public cancelButton: DialogButton = new DialogButton(Dialog.CANCEL_BUTTON_LABEL, true);
public customButtons: DialogButton[];
private _valid: boolean = true;
private _validityChangedEmitter = new Emitter<boolean>();
public readonly onValidityChanged = this._validityChangedEmitter.event;
constructor(public title: string, content?: string | DialogTab[]) {
if (content) {
this.content = content;
}
}
public get valid(): boolean {
return this._valid;
}
public notifyValidityChanged(valid: boolean) {
this._valid = valid;
this._validityChangedEmitter.fire(valid);
}
}
export class DialogButton implements sqlops.window.modelviewdialog.Button {

View File

@@ -20,6 +20,7 @@ export interface IModelView extends IView {
addToContainer(containerId: string, item: IItemConfig): void;
setLayout(componentId: string, layout: any): void;
setProperties(componentId: string, properties: { [key: string]: any }): void;
setValid(componentId: string, valid: boolean): void;
registerEvent(componentId: string);
onEvent: Event<any>;
}

View File

@@ -31,6 +31,7 @@ declare module 'sqlops' {
export interface ComponentBuilder<T extends Component> {
component(): T;
withProperties<U>(properties: U): ComponentBuilder<T>;
withValidation(validation: (component: T) => boolean): ComponentBuilder<T>;
}
export interface ContainerBuilder<T extends Component, TLayout, TItemLayout> extends ComponentBuilder<T> {
withLayout(layout: TLayout): ContainerBuilder<T, TLayout, TItemLayout>;
@@ -56,6 +57,21 @@ declare module 'sqlops' {
* @memberof Component
*/
updateProperties(properties: { [key: string]: any }): Thenable<boolean>;
/**
* Event fired to notify that the component's validity has changed
*/
readonly onValidityChanged: vscode.Event<boolean>;
/**
* Whether the component is valid or not
*/
readonly valid: boolean;
/**
* Run the component's validations
*/
validate(): void;
}
export interface FormComponent {
@@ -264,6 +280,21 @@ declare module 'sqlops' {
*/
readonly modelBuilder: ModelBuilder;
/**
* Whether or not the model view's root component is valid
*/
readonly valid: boolean;
/**
* Raised when the model view's valid property changes
*/
readonly onValidityChanged: vscode.Event<boolean>;
/**
* Run the model view root component's validations
*/
validate(): void;
/**
* Initializes the model with a root component definition.
* Once this has been done, the components will be laid out in the UI and
@@ -336,6 +367,16 @@ declare module 'sqlops' {
* Any additional buttons that should be displayed
*/
customButtons: Button[];
/**
* Whether the dialog's content is valid
*/
readonly valid: boolean;
/**
* Fired whenever the dialog's valid property changes
*/
readonly onValidityChanged: vscode.Event<boolean>;
}
export interface DialogTab {

View File

@@ -25,17 +25,23 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
navContainer(): sqlops.ContainerBuilder<sqlops.NavContainer, any, any> {
let id = this.getNextComponentId();
return new ContainerBuilderImpl(this._proxy, this._handle, ModelComponentTypes.NavContainer, id);
let container: ContainerBuilderImpl<sqlops.NavContainer, any, any> = new ContainerBuilderImpl(this._proxy, this._handle, ModelComponentTypes.NavContainer, id);
this._eventHandlers.set(id, container);
return container;
}
flexContainer(): sqlops.FlexBuilder {
let id = this.getNextComponentId();
return new ContainerBuilderImpl<sqlops.FlexContainer, sqlops.FlexLayout, sqlops.FlexItemLayout>(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id);
let container: ContainerBuilderImpl<sqlops.FlexContainer, any, any> = new ContainerBuilderImpl<sqlops.FlexContainer, sqlops.FlexLayout, sqlops.FlexItemLayout>(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id);
this._eventHandlers.set(id, container);
return container;
}
formContainer(): sqlops.FormBuilder {
let id = this.getNextComponentId();
return new FormContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Form, id);
let container = new FormContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Form, id);
this._eventHandlers.set(id, container);
return container;
}
card(): sqlops.ComponentBuilder<sqlops.CardComponent> {
@@ -110,6 +116,11 @@ class ComponentBuilderImpl<T extends sqlops.Component> implements sqlops.Compone
return this;
}
withValidation(validation: (component: T) => boolean): sqlops.ComponentBuilder<T> {
this._component.validations.push(validation);
return this;
}
handleEvent(eventArgs: IComponentEventArgs) {
this._component.onEvent(eventArgs);
}
@@ -138,6 +149,7 @@ class ContainerBuilderImpl<T extends sqlops.Component, TLayout, TItemLayout> ext
let componentWrapper = item as ComponentWrapper;
return new InternalItemConfig(componentWrapper, itemLayout);
});
components.forEach(component => component.onValidityChanged(() => this._component.validate()));
return this;
}
}
@@ -169,6 +181,7 @@ class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sq
this._component.itemConfigs.push(new InternalItemConfig(componentWrapper, itemLayout));
});
}
formItem.component.onValidityChanged(() => this._component.validate());
});
return this;
}
@@ -194,6 +207,10 @@ class ComponentWrapper implements sqlops.Component {
public properties: { [key: string]: any } = {};
public layout: any;
public itemConfigs: InternalItemConfig[];
public validations: ((component: ThisType<ComponentWrapper>) => boolean)[] = [];
private _valid: boolean = true;
private _onValidityChangedEmitter = new Emitter<boolean>();
public readonly onValidityChanged = this._onValidityChangedEmitter.event;
private _onErrorEmitter = new Emitter<Error>();
public readonly onError: vscode.Event<Error> = this._onErrorEmitter.event;
@@ -206,6 +223,12 @@ class ComponentWrapper implements sqlops.Component {
) {
this.properties = {};
this.itemConfigs = [];
this.validations.push((component: this) => {
return component.items.every(item => {
item.validate();
return item.valid;
});
});
}
public get id(): string {
@@ -248,6 +271,7 @@ class ComponentWrapper implements sqlops.Component {
}
let config = new InternalItemConfig(itemImpl, itemLayout);
this.itemConfigs.push(config);
itemImpl.onValidityChanged(() => this.validate());
this._proxy.$addToContainer(this._handle, this.id, config.toIItemConfig()).then(undefined, this.handleError);
}
@@ -270,18 +294,20 @@ class ComponentWrapper implements sqlops.Component {
public onEvent(eventArgs: IComponentEventArgs) {
if (eventArgs && eventArgs.eventType === ComponentEventType.PropertiesChanged) {
this.properties = eventArgs.args;
this.validate();
} else if (eventArgs) {
let emitter = this._emitterMap.get(eventArgs.eventType);
if (emitter) {
emitter.fire();
}
let emitter = this._emitterMap.get(eventArgs.eventType);
if (emitter) {
emitter.fire();
}
}
}
protected setProperty(key: string, value: any): Thenable<boolean> {
if (!this.properties[key] || this.properties[key] !== value) {
// Only notify the front end if a value has been updated
this.properties[key] = value;
this.validate();
return this.notifyPropertyChanged();
}
return Promise.resolve(true);
@@ -290,6 +316,29 @@ class ComponentWrapper implements sqlops.Component {
private handleError(err: Error): void {
this._onErrorEmitter.fire(err);
}
public validate(): void {
let isValid = true;
try {
this.validations.forEach(validation => {
if (!validation(this)) {
isValid = false;
}
});
} catch (e) {
isValid = false;
}
let oldValid = this._valid;
if (this._valid !== isValid) {
this._valid = isValid;
this._proxy.$notifyValidation(this._handle, this._id, isValid);
this._onValidityChangedEmitter.fire(this._valid);
}
}
public get valid(): boolean {
return this._valid;
}
}
class ContainerWrapper<T, U> extends ComponentWrapper implements sqlops.Container<T, U> {
@@ -428,8 +477,11 @@ class ButtonWrapper extends ComponentWrapper implements sqlops.ButtonComponent {
class ModelViewImpl implements sqlops.ModelView {
public onClosedEmitter = new Emitter<any>();
private _onValidityChangedEmitter = new Emitter<boolean>();
public readonly onValidityChanged = this._onValidityChangedEmitter.event;
private _modelBuilder: ModelBuilderImpl;
private _component: sqlops.Component;
constructor(
private readonly _proxy: MainThreadModelViewShape,
@@ -456,17 +508,28 @@ class ModelViewImpl implements sqlops.ModelView {
return this._modelBuilder;
}
public get valid(): boolean {
return this._component.valid;
}
public handleEvent(componentId: string, eventArgs: IComponentEventArgs): void {
this._modelBuilder.handleEvent(componentId, eventArgs);
}
public initializeModel<T extends sqlops.Component>(component: T): Thenable<void> {
component.onValidityChanged(valid => this._onValidityChangedEmitter.fire(valid));
this._component = component;
let componentImpl = <any>component as ComponentWrapper;
if (!componentImpl) {
return Promise.reject(nls.localize('unknownConfig', 'Unkown component configuration, must use ModelBuilder to create a configuration object'));
}
componentImpl.validate();
return this._proxy.$initializeModel(this._handle, componentImpl.toComponentShape());
}
public validate(): void {
this._component.validate();
}
}
export class ExtHostModelView implements ExtHostModelViewShape {

View File

@@ -21,10 +21,18 @@ class DialogImpl implements sqlops.window.modelviewdialog.Dialog {
public okButton: sqlops.window.modelviewdialog.Button;
public cancelButton: sqlops.window.modelviewdialog.Button;
public customButtons: sqlops.window.modelviewdialog.Button[];
public readonly onValidityChanged: vscode.Event<boolean>;
private _valid: boolean = true;
constructor(private _extHostModelViewDialog: ExtHostModelViewDialog) {
this.okButton = this._extHostModelViewDialog.createButton(nls.localize('dialogOkLabel', 'Done'));
this.cancelButton = this._extHostModelViewDialog.createButton(nls.localize('dialogCancelLabel', 'Cancel'));
this.onValidityChanged = this._extHostModelViewDialog.getValidityChangedEvent(this);
this.onValidityChanged(valid => this._valid = valid);
}
public get valid(): boolean {
return this._valid;
}
}
@@ -89,6 +97,7 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
private readonly _tabHandles = new Map<sqlops.window.modelviewdialog.DialogTab, number>();
private readonly _buttonHandles = new Map<sqlops.window.modelviewdialog.Button, number>();
private readonly _validityEmitters = new Map<number, Emitter<boolean>>();
private readonly _onClickCallbacks = new Map<number, () => void>();
constructor(
@@ -134,6 +143,13 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
this._onClickCallbacks.get(handle)();
}
public $onDialogValidityChanged(handle: number, valid: boolean): void {
let emitter = this._validityEmitters.get(handle);
if (emitter) {
emitter.fire(valid);
}
}
public open(dialog: sqlops.window.modelviewdialog.Dialog): void {
let handle = this.getDialogHandle(dialog);
this.updateDialogContent(dialog);
@@ -208,4 +224,14 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
button.label = label;
return button;
}
public getValidityChangedEvent(dialog: sqlops.window.modelviewdialog.Dialog) {
let handle = this.getDialogHandle(dialog);
let emitter = this._validityEmitters.get(handle);
if (!emitter) {
emitter = new Emitter<boolean>();
this._validityEmitters.set(handle, emitter);
}
return emitter.event;
}
}

View File

@@ -82,6 +82,10 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi
return this.execModelViewAction(handle, (modelView) => modelView.setProperties(componentId, properties));
}
$notifyValidation(handle: number, componentId: string, valid: boolean): Thenable<void> {
return this.execModelViewAction(handle, (modelView) => modelView.setValid(componentId, valid));
}
private execModelViewAction<T>(handle: number, action: (m: IModelView) => T): Thenable<T> {
let modelView: IModelView = this._dialogs.get(handle);
let result = action(modelView);

View File

@@ -52,6 +52,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
let cancelButton = this.getButton(details.cancelButton);
dialog.okButton = okButton;
dialog.cancelButton = cancelButton;
dialog.onValidityChanged(valid => this._proxy.$onDialogValidityChanged(handle, valid));
this._dialogs.set(handle, dialog);
}

View File

@@ -530,6 +530,7 @@ export interface MainThreadModelViewShape extends IDisposable {
$setLayout(handle: number, componentId: string, layout: any): Thenable<void>;
$setProperties(handle: number, componentId: string, properties: { [key: string]: any }): Thenable<void>;
$registerEvent(handle: number, componentId: string): Thenable<void>;
$notifyValidation(handle: number, componentId: string, valid: boolean): Thenable<void>;
}
export interface ExtHostObjectExplorerShape {
@@ -547,6 +548,7 @@ export interface MainThreadObjectExplorerShape extends IDisposable {
export interface ExtHostModelViewDialogShape {
$onButtonClick(handle: number): void;
$onDialogValidityChanged(handle: number, valid: boolean): void;
}
export interface MainThreadModelViewDialogShape extends IDisposable {