mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-26 01:25:38 -05:00
Add default model view input types and validation (#1397)
This commit is contained in:
@@ -24,7 +24,8 @@ export class ItemDescriptor<T> {
|
||||
|
||||
export abstract class ComponentBase extends Disposable implements IComponent, OnDestroy, OnInit {
|
||||
protected properties: { [key: string]: any; } = {};
|
||||
protected _valid: boolean = true;
|
||||
private _valid: boolean = true;
|
||||
protected _validations: (() => boolean | Thenable<boolean>)[] = [];
|
||||
private _eventQueue: IComponentEventArgs[] = [];
|
||||
constructor(
|
||||
protected _changeRef: ChangeDetectorRef) {
|
||||
@@ -44,6 +45,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
protected baseInit(): void {
|
||||
if (this.modelStore) {
|
||||
this.modelStore.registerComponent(this);
|
||||
this._validations.push(() => this.modelStore.validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +69,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
}
|
||||
this.properties = properties;
|
||||
this.layout();
|
||||
this.validate();
|
||||
}
|
||||
|
||||
protected getProperties<TPropertyBag>(): TPropertyBag {
|
||||
@@ -84,6 +87,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
eventType: ComponentEventType.PropertiesChanged,
|
||||
args: this.getProperties()
|
||||
});
|
||||
this.validate();
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
@@ -96,16 +100,6 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
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) {
|
||||
@@ -123,6 +117,21 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
this._eventQueue.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
public validate(): Thenable<boolean> {
|
||||
let validations = this._validations.map(validation => Promise.resolve(validation()));
|
||||
return Promise.all(validations).then(values => {
|
||||
let isValid = values.every(value => value === true);
|
||||
if (this._valid !== isValid) {
|
||||
this._valid = isValid;
|
||||
this.fireEvent({
|
||||
eventType: ComponentEventType.validityChanged,
|
||||
args: this._valid
|
||||
});
|
||||
}
|
||||
return isValid;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ContainerBase<T> extends ComponentBase {
|
||||
@@ -133,11 +142,17 @@ export abstract class ContainerBase<T> extends ComponentBase {
|
||||
) {
|
||||
super(_changeRef);
|
||||
this.items = [];
|
||||
this._validations.push(() => this.items.every(item => this.modelStore.getComponent(item.descriptor.id).valid));
|
||||
}
|
||||
|
||||
/// IComponent container-related implementation
|
||||
public addToContainer(componentDescriptor: IComponentDescriptor, config: any): void {
|
||||
this.items.push(new ItemDescriptor(componentDescriptor, config));
|
||||
this.modelStore.eventuallyRunOnComponent(componentDescriptor.id, component => component.registerEventHandler(event => {
|
||||
if (event.eventType === ComponentEventType.validityChanged) {
|
||||
this.validate();
|
||||
}
|
||||
}));
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,15 +52,17 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
return undefined;
|
||||
} else {
|
||||
return {
|
||||
content: nls.localize('invalidValueError', 'Invalid value'),
|
||||
content: this._input.inputElement.validationMessage || nls.localize('invalidValueError', 'Invalid value'),
|
||||
type: MessageType.ERROR
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
useDefaultValidation: true
|
||||
};
|
||||
|
||||
this._input = new InputBox(this._inputContainer.nativeElement, this._commonService.contextViewService, inputOptions);
|
||||
this._validations.push(() => !this._input.inputElement.validationMessage);
|
||||
|
||||
this._register(this._input);
|
||||
this._register(attachInputBoxStyler(this._input, this._commonService.themeService));
|
||||
@@ -74,6 +76,13 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
}
|
||||
}
|
||||
|
||||
public validate(): Thenable<boolean> {
|
||||
return super.validate().then(valid => {
|
||||
this._input.validate();
|
||||
return valid;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
@@ -91,6 +100,10 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
super.setProperties(properties);
|
||||
this._input.inputElement.type = this.inputType;
|
||||
if (this.inputType === 'number') {
|
||||
this._input.inputElement.step = 'any';
|
||||
}
|
||||
this._input.value = this.value;
|
||||
this._input.setAriaLabel(this.ariaLabel);
|
||||
this._input.setPlaceHolder(this.placeHolder);
|
||||
@@ -98,11 +111,8 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
if (this.width) {
|
||||
this._input.width = this.width;
|
||||
}
|
||||
}
|
||||
|
||||
public setValid(valid: boolean): void {
|
||||
super.setValid(valid);
|
||||
this._input.validate();
|
||||
this._input.inputElement.required = this.required;
|
||||
this.validate();
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
@@ -146,4 +156,20 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
public set width(newValue: number) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, number>((props, value) => props.width = value, newValue);
|
||||
}
|
||||
|
||||
public get inputType(): string {
|
||||
return this.getPropertyOrDefault<sqlops.InputBoxProperties, string>((props) => props.inputType, 'text');
|
||||
}
|
||||
|
||||
public set inputType(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, string>((props, value) => props.inputType = value, newValue);
|
||||
}
|
||||
|
||||
public get required(): boolean {
|
||||
return this.getPropertyOrDefault<sqlops.InputBoxProperties, boolean>((props) => props.required, false);
|
||||
}
|
||||
|
||||
public set required(newValue: boolean) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, boolean>((props, value) => props.required = value, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface IComponent {
|
||||
setLayout?: (layout: any) => void;
|
||||
setProperties?: (properties: { [key: string]: any; }) => void;
|
||||
readonly valid?: boolean;
|
||||
setValid(valid: boolean): void;
|
||||
validate(): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export const COMPONENT_CONFIG = new InjectionToken<IComponentConfig>('component_config');
|
||||
@@ -88,4 +88,12 @@ export interface IModelStore {
|
||||
* @memberof IModelStore
|
||||
*/
|
||||
eventuallyRunOnComponent<T>(componentId: string, action: (component: IComponent) => T): Promise<T>;
|
||||
/**
|
||||
* Register a callback that will validate components when given a component ID
|
||||
*/
|
||||
registerValidationCallback(callback: (componentId: string) => Thenable<boolean>): void;
|
||||
/**
|
||||
* Run all validations for the given component and return the new validation value
|
||||
*/
|
||||
validate(component: IComponent): Thenable<boolean>;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ export class ModelStore implements IModelStore {
|
||||
private _descriptorMappings: { [x: string]: IComponentDescriptor } = {};
|
||||
private _componentMappings: { [x: string]: IComponent } = {};
|
||||
private _componentActions: { [x: string]: Deferred<IComponent> } = {};
|
||||
private _validationCallbacks: ((componentId: string) => Thenable<boolean>)[] = [];
|
||||
constructor() {
|
||||
}
|
||||
|
||||
@@ -66,6 +67,15 @@ export class ModelStore implements IModelStore {
|
||||
}
|
||||
}
|
||||
|
||||
registerValidationCallback(callback: (componentId: string) => Thenable<boolean>): void {
|
||||
this._validationCallbacks.push(callback);
|
||||
}
|
||||
|
||||
validate(component: IComponent): Thenable<boolean> {
|
||||
let componentId = Object.entries(this._componentMappings).find(([id, mappedComponent]) => component === mappedComponent)[0];
|
||||
return Promise.all(this._validationCallbacks.map(callback => callback(componentId))).then(validations => validations.every(validation => validation === true));
|
||||
}
|
||||
|
||||
private addPendingAction<T>(componentId: string, action: (component: IComponent) => T): Promise<T> {
|
||||
// We create a promise and chain it onto a tracking promise whose resolve method
|
||||
// will only be called once the component is created
|
||||
|
||||
@@ -39,9 +39,10 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
private _onEventEmitter = new Emitter<any>();
|
||||
|
||||
|
||||
initializeModel(rootComponent: IComponentShape): void {
|
||||
initializeModel(rootComponent: IComponentShape, validationCallback: (componentId: string) => Thenable<boolean>): void {
|
||||
let descriptor = this.defineComponent(rootComponent);
|
||||
this.rootDescriptor = descriptor;
|
||||
this.modelStore.registerValidationCallback(validationCallback);
|
||||
// Kick off the build by detecting changes to the model
|
||||
this.changeRef.detectChanges();
|
||||
}
|
||||
@@ -91,10 +92,6 @@ 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
|
||||
@@ -113,4 +110,8 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
public get onEvent(): Event<IComponentEventArgs> {
|
||||
return this._onEventEmitter.event;
|
||||
}
|
||||
|
||||
public validate(componentId: string): Thenable<boolean> {
|
||||
return new Promise(resolve => this.modelStore.eventuallyRunOnComponent(componentId, component => resolve(component.validate())));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user