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);
}));
});
}