Add default model view input types and validation (#1397)

This commit is contained in:
Matt Irvine
2018-05-14 16:20:19 -07:00
committed by GitHub
parent 89c48bbe75
commit 9bd45cf66a
14 changed files with 363 additions and 118 deletions

View File

@@ -18,7 +18,7 @@ import { IActionDescriptor } from 'vs/editor/standalone/browser/standaloneCodeEd
class ModelBuilderImpl implements sqlops.ModelBuilder {
private nextComponentId: number;
private readonly _eventHandlers = new Map<string, IWithEventHandler>();
private readonly _componentBuilders = new Map<string, ComponentBuilderImpl<any>>();
constructor(private readonly _proxy: MainThreadModelViewShape, private readonly _handle: number) {
this.nextComponentId = 0;
@@ -27,72 +27,91 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
navContainer(): sqlops.ContainerBuilder<sqlops.NavContainer, any, any> {
let id = this.getNextComponentId();
let container: ContainerBuilderImpl<sqlops.NavContainer, any, any> = new ContainerBuilderImpl(this._proxy, this._handle, ModelComponentTypes.NavContainer, id);
this._eventHandlers.set(id, container);
this._componentBuilders.set(id, container);
return container;
}
flexContainer(): sqlops.FlexBuilder {
let id = this.getNextComponentId();
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);
this._componentBuilders.set(id, container);
return container;
}
formContainer(): sqlops.FormBuilder {
let id = this.getNextComponentId();
let container = new FormContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Form, id);
this._eventHandlers.set(id, container);
this._componentBuilders.set(id, container);
return container;
}
card(): sqlops.ComponentBuilder<sqlops.CardComponent> {
let id = this.getNextComponentId();
return this.withEventHandler(new CardWrapper(this._proxy, this._handle, id), id);
let builder: ComponentBuilderImpl<sqlops.CardComponent> = this.getComponentBuilder(new CardWrapper(this._proxy, this._handle, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
inputBox(): sqlops.ComponentBuilder<sqlops.InputBoxComponent> {
let id = this.getNextComponentId();
return this.withEventHandler(new InputBoxWrapper(this._proxy, this._handle, id), id);
let builder: ComponentBuilderImpl<sqlops.InputBoxComponent> = this.getComponentBuilder(new InputBoxWrapper(this._proxy, this._handle, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
checkBox(): sqlops.ComponentBuilder<sqlops.CheckBoxComponent> {
let id = this.getNextComponentId();
return this.withEventHandler(new CheckBoxWrapper(this._proxy, this._handle, id), id);
let builder: ComponentBuilderImpl<sqlops.CheckBoxComponent> = this.getComponentBuilder(new CheckBoxWrapper(this._proxy, this._handle, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
button(): sqlops.ComponentBuilder<sqlops.ButtonComponent> {
let id = this.getNextComponentId();
return this.withEventHandler(new ButtonWrapper(this._proxy, this._handle, id), id);
let builder: ComponentBuilderImpl<sqlops.ButtonComponent> = this.getComponentBuilder(new ButtonWrapper(this._proxy, this._handle, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
dropDown(): sqlops.ComponentBuilder<sqlops.DropDownComponent> {
let id = this.getNextComponentId();
return this.withEventHandler(new DropDownWrapper(this._proxy, this._handle, id), id);
let builder: ComponentBuilderImpl<sqlops.DropDownComponent> = this.getComponentBuilder(new DropDownWrapper(this._proxy, this._handle, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
dashboardWidget(widgetId: string): sqlops.ComponentBuilder<sqlops.WidgetComponent> {
let id = this.getNextComponentId();
return this.withEventHandler<sqlops.WidgetComponent>(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id);
let builder = this.getComponentBuilder<sqlops.WidgetComponent>(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
dashboardWebview(webviewId: string): sqlops.ComponentBuilder<sqlops.WebviewComponent> {
let id = this.getNextComponentId();
return this.withEventHandler(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWebview, id), id);
let builder: ComponentBuilderImpl<sqlops.WebviewComponent> = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWebview, id), id);
this._componentBuilders.set(id, builder);
return builder;
}
withEventHandler<T extends sqlops.Component>(component: ComponentWrapper, id: string): sqlops.ComponentBuilder<T> {
getComponentBuilder<T extends sqlops.Component>(component: ComponentWrapper, id: string): ComponentBuilderImpl<T> {
let componentBuilder: ComponentBuilderImpl<T> = new ComponentBuilderImpl<T>(component);
this._eventHandlers.set(id, componentBuilder);
this._componentBuilders.set(id, componentBuilder);
return componentBuilder;
}
handleEvent(componentId: string, eventArgs: IComponentEventArgs): void {
let eventHandler = this._eventHandlers.get(componentId);
let eventHandler = this._componentBuilders.get(componentId);
if (eventHandler) {
eventHandler.handleEvent(eventArgs);
}
}
public runCustomValidations(componentId: string): boolean {
let component = this._componentBuilders.get(componentId).componentWrapper();
return component.runCustomValidations();
}
private getNextComponentId(): string {
return `component${this._handle}_${this.nextComponentId++}`;
}
@@ -112,13 +131,17 @@ class ComponentBuilderImpl<T extends sqlops.Component> implements sqlops.Compone
return <T><any>this._component;
}
componentWrapper(): ComponentWrapper {
return this._component;
}
withProperties<U>(properties: U): sqlops.ComponentBuilder<T> {
this._component.properties = properties;
return this;
}
withValidation(validation: (component: T) => boolean): sqlops.ComponentBuilder<T> {
this._component.validations.push(validation);
this._component.customValidations.push(validation);
return this;
}
@@ -150,7 +173,6 @@ 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;
}
}
@@ -182,7 +204,6 @@ class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sq
this._component.itemConfigs.push(new InternalItemConfig(componentWrapper, itemLayout));
});
}
formItem.component.onValidityChanged(() => this._component.validate());
});
return this;
}
@@ -208,7 +229,7 @@ class ComponentWrapper implements sqlops.Component {
public properties: { [key: string]: any } = {};
public layout: any;
public itemConfigs: InternalItemConfig[];
public validations: ((component: ThisType<ComponentWrapper>) => boolean)[] = [];
public customValidations: ((component: ThisType<ComponentWrapper>) => boolean)[] = [];
private _valid: boolean = true;
private _onValidityChangedEmitter = new Emitter<boolean>();
public readonly onValidityChanged = this._onValidityChangedEmitter.event;
@@ -224,12 +245,6 @@ 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 {
@@ -280,7 +295,6 @@ 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);
}
@@ -303,7 +317,9 @@ class ComponentWrapper implements sqlops.Component {
public onEvent(eventArgs: IComponentEventArgs) {
if (eventArgs && eventArgs.eventType === ComponentEventType.PropertiesChanged) {
this.properties = eventArgs.args;
this.validate();
}
else if (eventArgs && eventArgs.eventType === ComponentEventType.validityChanged) {
this._valid = eventArgs.args;
} else if (eventArgs) {
let emitter = this._emitterMap.get(eventArgs.eventType);
if (emitter) {
@@ -312,11 +328,10 @@ class ComponentWrapper implements sqlops.Component {
}
}
protected setProperty(key: string, value: any): Thenable<boolean> {
protected async setProperty(key: string, value: any): Promise<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);
@@ -326,10 +341,10 @@ class ComponentWrapper implements sqlops.Component {
this._onErrorEmitter.fire(err);
}
public validate(): void {
public runCustomValidations(): boolean {
let isValid = true;
try {
this.validations.forEach(validation => {
this.customValidations.forEach(validation => {
if (!validation(this)) {
isValid = false;
}
@@ -337,12 +352,11 @@ class ComponentWrapper implements sqlops.Component {
} 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);
}
return isValid;
}
public validate() {
return this._proxy.$validate(this._handle, this._id);
}
public get valid(): boolean {
@@ -434,6 +448,13 @@ class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxCompone
this.setProperty('width', v);
}
public get inputType(): sqlops.InputBoxInputType {
return this.properties['inputType'];
}
public set inputType(v: sqlops.InputBoxInputType) {
this.setProperty('inputType', v);
}
public get onTextChanged(): vscode.Event<any> {
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
return emitter && emitter.event;
@@ -566,12 +587,15 @@ class ModelViewImpl implements sqlops.ModelView {
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();
public validate(): Thenable<boolean> {
return this._proxy.$validate(this._handle, this._component.id);
}
public runCustomValidations(componentId: string): boolean {
return this._modelBuilder.runCustomValidations(componentId);
}
}
@@ -610,4 +634,9 @@ export class ExtHostModelView implements ExtHostModelViewShape {
view.handleEvent(componentId, eventArgs);
}
}
$runCustomValidations(handle: number, componentId: string): Thenable<boolean> {
const view = this._modelViews.get(handle);
return Promise.resolve(view.runCustomValidations(componentId));
}
}

View File

@@ -46,7 +46,7 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi
$initializeModel(handle: number, rootComponent: IComponentShape): Thenable<void> {
return this.execModelViewAction(handle, (modelView) => {
modelView.initializeModel(rootComponent);
modelView.initializeModel(rootComponent, (componentId) => this.runCustomValidations(handle, componentId));
});
}
@@ -82,8 +82,12 @@ 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));
$validate(handle: number, componentId: string): Thenable<boolean> {
return new Promise(resolve => this.execModelViewAction(handle, (modelView) => resolve(modelView.validate(componentId))));
}
private runCustomValidations(handle: number, componentId: string): Thenable<boolean> {
return this._proxy.$runCustomValidations(handle, componentId);
}
private execModelViewAction<T>(handle: number, action: (m: IModelView) => T): Thenable<T> {

View File

@@ -520,6 +520,7 @@ export interface ExtHostModelViewShape {
$onClosed(handle: number): void;
$registerWidget(handle: number, id: string, connection: sqlops.connection.Connection, serverInfo: sqlops.ServerInfo): void;
$handleEvent(handle: number, id: string, eventArgs: any);
$runCustomValidations(handle: number, id: string): Thenable<boolean>;
}
export interface MainThreadModelViewShape extends IDisposable {
@@ -530,7 +531,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>;
$validate(handle: number, componentId: string): Thenable<boolean>;
}
export interface ExtHostObjectExplorerShape {