Add dialog close validation (#1704)

This commit is contained in:
Matt Irvine
2018-06-21 16:55:47 -07:00
committed by GitHub
parent 1871fd383e
commit 6c5fac997f
8 changed files with 84 additions and 5 deletions

View File

@@ -120,11 +120,13 @@ export class DialogModal extends Modal {
this.show(); this.show();
} }
public done(): void { public async done(): Promise<void> {
if (this._dialog.okButton.enabled) { if (this._dialog.okButton.enabled) {
this._onDone.fire(); if (await this._dialog.validateClose()) {
this.dispose(); this._onDone.fire();
this.hide(); this.dispose();
this.hide();
}
} }
} }

View File

@@ -49,6 +49,7 @@ export class Dialog extends ModelViewPane {
private _onMessageChange = new Emitter<DialogMessage>(); private _onMessageChange = new Emitter<DialogMessage>();
public readonly onMessageChange = this._onMessageChange.event; public readonly onMessageChange = this._onMessageChange.event;
private _message: DialogMessage; private _message: DialogMessage;
private _closeValidator: () => boolean | Thenable<boolean>;
constructor(public title: string, content?: string | DialogTab[]) { constructor(public title: string, content?: string | DialogTab[]) {
super(); super();
@@ -67,6 +68,18 @@ export class Dialog extends ModelViewPane {
this._onMessageChange.fire(this._message); this._onMessageChange.fire(this._message);
} }
} }
public registerCloseValidator(validator: () => boolean | Thenable<boolean>): void {
this._closeValidator = validator;
}
public validateClose(): Thenable<boolean> {
if (this._closeValidator) {
return Promise.resolve(this._closeValidator());
} else {
return Promise.resolve(true);
}
}
} }
export class DialogButton implements sqlops.window.modelviewdialog.Button { export class DialogButton implements sqlops.window.modelviewdialog.Button {

View File

@@ -660,6 +660,15 @@ declare module 'sqlops' {
* undefined or the text is empty or undefined. The default level is error. * undefined or the text is empty or undefined. The default level is error.
*/ */
message: DialogMessage; message: DialogMessage;
/**
* Register a callback that will be called when the user tries to click done. Only
* one callback can be registered at once, so each registration call will clear
* the previous registration.
* @param validator The callback that gets executed when the user tries to click
* done. Return true to allow the dialog to close or false to block it from closing
*/
registerCloseValidator(validator: () => boolean | Thenable<boolean>): void;
} }
export interface DialogTab extends ModelViewPanel { export interface DialogTab extends ModelViewPanel {

View File

@@ -94,6 +94,7 @@ class DialogImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdi
public cancelButton: sqlops.window.modelviewdialog.Button; public cancelButton: sqlops.window.modelviewdialog.Button;
public customButtons: sqlops.window.modelviewdialog.Button[]; public customButtons: sqlops.window.modelviewdialog.Button[];
private _message: sqlops.window.modelviewdialog.DialogMessage; private _message: sqlops.window.modelviewdialog.DialogMessage;
private _closeValidator: () => boolean | Thenable<boolean>;
constructor(extHostModelViewDialog: ExtHostModelViewDialog, constructor(extHostModelViewDialog: ExtHostModelViewDialog,
extHostModelView: ExtHostModelViewShape) { extHostModelView: ExtHostModelViewShape) {
@@ -115,6 +116,18 @@ class DialogImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdi
this._message = value; this._message = value;
this._extHostModelViewDialog.updateDialogContent(this); this._extHostModelViewDialog.updateDialogContent(this);
} }
public registerCloseValidator(validator: () => boolean | Thenable<boolean>): void {
this._closeValidator = validator;
}
public validateClose(): Thenable<boolean> {
if (this._closeValidator) {
return Promise.resolve(this._closeValidator());
} else {
return Promise.resolve(true);
}
}
} }
class TabImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.DialogTab { class TabImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.DialogTab {
@@ -374,6 +387,11 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
return wizard.validateNavigation(info); return wizard.validateNavigation(info);
} }
public $validateDialogClose(handle: number): Thenable<boolean> {
let dialog = this._objectsByHandle.get(handle) as DialogImpl;
return dialog.validateClose();
}
public openDialog(dialog: sqlops.window.modelviewdialog.Dialog): void { public openDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
let handle = this.getHandle(dialog); let handle = this.getHandle(dialog);
this.updateDialogContent(dialog); this.updateDialogContent(dialog);

View File

@@ -80,6 +80,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
dialog.okButton = okButton; dialog.okButton = okButton;
dialog.cancelButton = cancelButton; dialog.cancelButton = cancelButton;
dialog.onValidityChanged(valid => this._proxy.$onPanelValidityChanged(handle, valid)); dialog.onValidityChanged(valid => this._proxy.$onPanelValidityChanged(handle, valid));
dialog.registerCloseValidator(() => this.validateDialogClose(handle));
this._dialogs.set(handle, dialog); this._dialogs.set(handle, dialog);
} }
@@ -262,4 +263,8 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
private validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean> { private validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean> {
return this._proxy.$validateNavigation(handle, info); return this._proxy.$validateNavigation(handle, info);
} }
private validateDialogClose(handle: number): Thenable<boolean> {
return this._proxy.$validateDialogClose(handle);
}
} }

View File

@@ -563,6 +563,7 @@ export interface ExtHostModelViewDialogShape {
$onWizardPageChanged(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): void; $onWizardPageChanged(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): void;
$updateWizardPageInfo(handle: number, pageHandles: number[], currentPageIndex: number): void; $updateWizardPageInfo(handle: number, pageHandles: number[], currentPageIndex: number): void;
$validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean>; $validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean>;
$validateDialogClose(handle: number): Thenable<boolean>;
} }
export interface MainThreadModelViewDialogShape extends IDisposable { export interface MainThreadModelViewDialogShape extends IDisposable {

View File

@@ -307,4 +307,23 @@ suite('ExtHostModelViewDialog Tests', () => {
// Then the main thread gets notified of the new details // Then the main thread gets notified of the new details
mockProxy.verify(x => x.$setWizardDetails(It.isAny(), It.is(x => x.message === newMessage)), Times.once()); mockProxy.verify(x => x.$setWizardDetails(It.isAny(), It.is(x => x.message === newMessage)), Times.once());
}); });
test('Main thread can execute dialog close validation', () => {
// Set up the main thread mock to record the dialog handle
let dialogHandle: number;
mockProxy.setup(x => x.$setDialogDetails(It.isAny(), It.isAny())).callback((handle, details) => dialogHandle = handle);
// Create the dialog and add a validation that records that it has been called
let dialog = extHostModelViewDialog.createDialog('dialog_1');
extHostModelViewDialog.updateDialogContent(dialog);
let callCount = 0;
dialog.registerCloseValidator(() => {
callCount++;
return true;
});
// If I call the validation from the main thread then it should run
extHostModelViewDialog.$validateDialogClose(dialogHandle);
assert.equal(callCount, 1);
});
}); });

View File

@@ -60,7 +60,8 @@ suite('MainThreadModelViewDialog Tests', () => {
$onPanelValidityChanged: (handle, valid) => undefined, $onPanelValidityChanged: (handle, valid) => undefined,
$onWizardPageChanged: (handle, info) => undefined, $onWizardPageChanged: (handle, info) => undefined,
$updateWizardPageInfo: (wizardHandle, pageHandles, currentPageIndex) => undefined, $updateWizardPageInfo: (wizardHandle, pageHandles, currentPageIndex) => undefined,
$validateNavigation: (handle, info) => undefined $validateNavigation: (handle, info) => undefined,
$validateDialogClose: handle => undefined
}); });
let extHostContext = <IExtHostContext>{ let extHostContext = <IExtHostContext>{
getProxy: proxyType => mockExtHostModelViewDialog.object getProxy: proxyType => mockExtHostModelViewDialog.object
@@ -347,4 +348,15 @@ suite('MainThreadModelViewDialog Tests', () => {
assert.equal(newMessage, wizardDetails.message, 'New message was not included in the fired event'); assert.equal(newMessage, wizardDetails.message, 'New message was not included in the fired event');
assert.equal(openedWizard.message, wizardDetails.message, 'New message was not set on the wizard'); assert.equal(openedWizard.message, wizardDetails.message, 'New message was not set on the wizard');
}); });
test('Creating a dialog adds a close validation that calls the extension host', () => {
mockExtHostModelViewDialog.setup(x => x.$validateDialogClose(It.isAny()));
// If I call validateClose on the dialog that gets created
mainThreadModelViewDialog.$openDialog(dialogHandle);
openedDialog.validateClose();
// Then the call gets forwarded to the extension host
mockExtHostModelViewDialog.verify(x => x.$validateDialogClose(It.is(handle => handle === dialogHandle)), Times.once());
});
}); });