diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts index 220f40d7c0..20a72d8e9e 100644 --- a/samples/sqlservices/src/controllers/mainController.ts +++ b/samples/sqlservices/src/controllers/mainController.ts @@ -199,6 +199,7 @@ export default class MainController implements vscode.Disposable { dropdown.onValueChanged((params) => { vscode.window.showInformationMessage(inputBox2.value); inputBox.value = dropdown.value.toString(); + console.info('dropdown change ' + dropdown.value.toString()); }); let radioButton = view.modelBuilder.radioButton() .withProperties({ @@ -231,14 +232,7 @@ export default class MainController implements vscode.Disposable { }).withItems([ form2Model ]).component(); - radioButton.onDidClick(() => { - inputBox.value = radioButton.value; - groupModel1.enabled = true; - }); - radioButton2.onDidClick(() => { - inputBox.value = radioButton.value; - groupModel1.enabled = false; - }); + let table = view.modelBuilder.table().withProperties({ data: [ ['1', '2', '2'], @@ -303,17 +297,12 @@ export default class MainController implements vscode.Disposable { }).withItems([ radioButton, groupModel1, radioButton2] , { flex: '1 1 50%' }).component(); - let formModel = view.modelBuilder.formContainer() + let formItemLayout = { + horizontal: false, + componentWidth: componentWidth + }; + let formBuilder = view.modelBuilder.formContainer() .withFormItems([{ - component: inputBoxWrapper, - title: 'Backup name' - }, { - component: inputBox2, - title: 'Recovery model' - }, { - component: dropdown, - title: 'Backup type' - }, { component: checkbox, title: '' }, { @@ -326,16 +315,56 @@ export default class MainController implements vscode.Disposable { }, { component: declarativeTable, title: 'Declarative Table' - }, { - component: table, - title: 'Table' - }, { - component: listBox, - title: 'List Box' - }], { - horizontal: false, - componentWidth: componentWidth - }).component(); + }], formItemLayout); + let groupItems = { + components: [{ + component: table, + title: 'Table' + }, { + component: listBox, + title: 'List Box' + }], title: 'group'}; + formBuilder.addFormItem(groupItems, formItemLayout); + + formBuilder.insertFormItem({ + component: inputBoxWrapper, + title: 'Backup name' + }, 0, formItemLayout); + formBuilder.insertFormItem({ + component: inputBox2, + title: 'Recovery model' + }, 1, formItemLayout); + formBuilder.insertFormItem({ + component: dropdown, + title: 'Backup type' + }, 2, formItemLayout); + let formModel = formBuilder.component(); + let inputBox6 = view.modelBuilder.inputBox().component(); + inputBox6.onTextChanged(e => { + console.info('textbox6 changed: ' + inputBox6.value); + }); + radioButton.onDidClick(() => { + inputBox.value = radioButton.value; + groupModel1.enabled = true; + + formBuilder.insertFormItem({ + component: dropdown, + title: 'Backup type' + }, 2, formItemLayout); + flexRadioButtonsModel.addItem(inputBox6, { flex: '1 1 50%' }); + formBuilder.addFormItem(groupItems, formItemLayout); + }); + + radioButton2.onDidClick(() => { + inputBox.value = radioButton.value; + groupModel1.enabled = false; + formBuilder.removeFormItem({ + component: dropdown, + title: 'Backup type' + }); + flexRadioButtonsModel.removeItem(inputBox6); + formBuilder.removeFormItem(groupItems); + }); let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); formWrapper.loading = false; customButton2.onClick(() => { diff --git a/src/sql/parts/modelComponents/button.component.ts b/src/sql/parts/modelComponents/button.component.ts index 3438023b33..08bba4ffe1 100644 --- a/src/sql/parts/modelComponents/button.component.ts +++ b/src/sql/parts/modelComponents/button.component.ts @@ -91,10 +91,6 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(layout: any): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/card.component.ts b/src/sql/parts/modelComponents/card.component.ts index ba5c74a171..945ac0e275 100644 --- a/src/sql/parts/modelComponents/card.component.ts +++ b/src/sql/parts/modelComponents/card.component.ts @@ -81,10 +81,6 @@ export default class CardComponent extends ComponentWithIconBase implements ICom } /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout (layout: any): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/checkbox.component.ts b/src/sql/parts/modelComponents/checkbox.component.ts index e7bd023307..c52f2e8b0e 100644 --- a/src/sql/parts/modelComponents/checkbox.component.ts +++ b/src/sql/parts/modelComponents/checkbox.component.ts @@ -63,10 +63,6 @@ export default class CheckBoxComponent extends ComponentBase implements ICompone /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(layout: any): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/componentBase.ts b/src/sql/parts/modelComponents/componentBase.ts index 15b0fb2bcd..1feb5d2023 100644 --- a/src/sql/parts/modelComponents/componentBase.ts +++ b/src/sql/parts/modelComponents/componentBase.ts @@ -21,6 +21,7 @@ import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentW import URI from 'vs/base/common/uri'; import { IdGenerator } from 'vs/base/common/idGenerator'; import { createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; +import * as nls from 'vs/nls'; export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI }; @@ -46,7 +47,9 @@ export abstract class ComponentBase extends Disposable implements IComponent, On protected _onEventEmitter = new Emitter(); public layout(): void { - this._changeRef.detectChanges(); + if (!this._changeRef['destroyed']) { + this._changeRef.detectChanges(); + } } protected baseInit(): void { @@ -222,17 +225,34 @@ export abstract class ContainerBase extends ComponentBase { } /// IComponent container-related implementation - public addToContainer(componentDescriptor: IComponentDescriptor, config: any): void { + public addToContainer(componentDescriptor: IComponentDescriptor, config: any, index?: number): void { if (this.items.some(item => item.descriptor.id === componentDescriptor.id && item.descriptor.type === componentDescriptor.type)) { return; } - this.items.push(new ItemDescriptor(componentDescriptor, config)); + if (index !== undefined && index !== null && index >= 0 && index < this.items.length) { + this.items.splice(index, 0, new ItemDescriptor(componentDescriptor, config)); + } else if(!index) { + this.items.push(new ItemDescriptor(componentDescriptor, config)); + } else { + throw new Error(nls.localize('invalidIndex', 'The index is invalid.')); + } this.modelStore.eventuallyRunOnComponent(componentDescriptor.id, component => component.registerEventHandler(event => { if (event.eventType === ComponentEventType.validityChanged) { this.validate(); } })); this._changeRef.detectChanges(); + return; + } + + public removeFromContainer(componentDescriptor: IComponentDescriptor): boolean { + let index = this.items.findIndex(item => item.descriptor.id === componentDescriptor.id && item.descriptor.type === componentDescriptor.type); + if (index >= 0) { + this.items.splice(index, 1); + this._changeRef.detectChanges(); + return true; + } + return false; } public clearContainer(): void { diff --git a/src/sql/parts/modelComponents/declarativeTable.component.ts b/src/sql/parts/modelComponents/declarativeTable.component.ts index 795f8f1141..c889e938d1 100644 --- a/src/sql/parts/modelComponents/declarativeTable.component.ts +++ b/src/sql/parts/modelComponents/declarativeTable.component.ts @@ -189,10 +189,6 @@ export default class DeclarativeTableComponent extends ComponentBase implements /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(layout: any): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/dropdown.component.ts b/src/sql/parts/modelComponents/dropdown.component.ts index 0173a3e431..57bbd7e53e 100644 --- a/src/sql/parts/modelComponents/dropdown.component.ts +++ b/src/sql/parts/modelComponents/dropdown.component.ts @@ -98,10 +98,6 @@ export default class DropDownComponent extends ComponentBase implements ICompone /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(layout: any): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/fileBrowserTree.component.ts b/src/sql/parts/modelComponents/fileBrowserTree.component.ts index 6c91d36eb7..6a6264e7f2 100644 --- a/src/sql/parts/modelComponents/fileBrowserTree.component.ts +++ b/src/sql/parts/modelComponents/fileBrowserTree.component.ts @@ -104,10 +104,6 @@ export default class FileBrowserTreeComponent extends ComponentBase implements I /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/inputbox.component.ts b/src/sql/parts/modelComponents/inputbox.component.ts index 69d43b56ac..edbd651b53 100644 --- a/src/sql/parts/modelComponents/inputbox.component.ts +++ b/src/sql/parts/modelComponents/inputbox.component.ts @@ -152,7 +152,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone /// IComponent implementation public layout(): void { - this._changeRef.detectChanges(); + super.layout(); this.layoutInputBox(); } diff --git a/src/sql/parts/modelComponents/interfaces.ts b/src/sql/parts/modelComponents/interfaces.ts index fdbbc11ef1..17a42b1bb5 100644 --- a/src/sql/parts/modelComponents/interfaces.ts +++ b/src/sql/parts/modelComponents/interfaces.ts @@ -14,13 +14,14 @@ import { IDisposable } from 'vs/base/common/lifecycle'; * @export * @interface IComponent */ -export interface IComponent { +export interface IComponent extends IDisposable { descriptor: IComponentDescriptor; modelStore: IModelStore; layout(); registerEventHandler(handler: (event: IComponentEventArgs) => void): IDisposable; clearContainer?: () => void; - addToContainer?: (componentDescriptor: IComponentDescriptor, config: any) => void; + addToContainer?: (componentDescriptor: IComponentDescriptor, config: any, index?: number) => void; + removeFromContainer?: (componentDescriptor: IComponentDescriptor) => void; setLayout?: (layout: any) => void; setProperties?: (properties: { [key: string]: any; }) => void; enabled: boolean; diff --git a/src/sql/parts/modelComponents/listbox.component.ts b/src/sql/parts/modelComponents/listbox.component.ts index 81db438b09..c375173024 100644 --- a/src/sql/parts/modelComponents/listbox.component.ts +++ b/src/sql/parts/modelComponents/listbox.component.ts @@ -75,11 +75,6 @@ export default class ListBoxComponent extends ComponentBase implements IComponen } /// IComponent implementation - - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(layout: any): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/loadingComponent.component.ts b/src/sql/parts/modelComponents/loadingComponent.component.ts index 3cc15443a1..b9c234bf7c 100644 --- a/src/sql/parts/modelComponents/loadingComponent.component.ts +++ b/src/sql/parts/modelComponents/loadingComponent.component.ts @@ -60,10 +60,6 @@ export default class LoadingComponent extends ComponentBase implements IComponen /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(): void { this.layout(); } diff --git a/src/sql/parts/modelComponents/modelStore.ts b/src/sql/parts/modelComponents/modelStore.ts index 65d0793946..c64f155a9b 100644 --- a/src/sql/parts/modelComponents/modelStore.ts +++ b/src/sql/parts/modelComponents/modelStore.ts @@ -51,6 +51,7 @@ export class ModelStore implements IModelStore { let id = component.descriptor.id; this._componentMappings[id] = undefined; this._componentActions[id] = undefined; + this._descriptorMappings[id] = undefined; // TODO notify model for cleanup } diff --git a/src/sql/parts/modelComponents/radioButton.component.ts b/src/sql/parts/modelComponents/radioButton.component.ts index 80e98ae987..9b0cf3e661 100644 --- a/src/sql/parts/modelComponents/radioButton.component.ts +++ b/src/sql/parts/modelComponents/radioButton.component.ts @@ -65,10 +65,6 @@ export default class RadioButtonComponent extends ComponentBase implements IComp /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(layout: any): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/table.component.ts b/src/sql/parts/modelComponents/table.component.ts index c6a16a3481..6845c8c0c2 100644 --- a/src/sql/parts/modelComponents/table.component.ts +++ b/src/sql/parts/modelComponents/table.component.ts @@ -132,8 +132,7 @@ export default class TableComponent extends ComponentBase implements IComponent, public layout(): void { this.layoutTable(); - - this._changeRef.detectChanges(); + super.layout(); } private layoutTable(): void { diff --git a/src/sql/parts/modelComponents/text.component.ts b/src/sql/parts/modelComponents/text.component.ts index 00ddc6161e..4d92ef7637 100644 --- a/src/sql/parts/modelComponents/text.component.ts +++ b/src/sql/parts/modelComponents/text.component.ts @@ -43,10 +43,6 @@ export default class TextComponent extends ComponentBase implements IComponent, /// IComponent implementation - public layout(): void { - this._changeRef.detectChanges(); - } - public setLayout(layout: any): void { // TODO allow configuring the look and feel this.layout(); diff --git a/src/sql/parts/modelComponents/tree/tree.component.ts b/src/sql/parts/modelComponents/tree/tree.component.ts index d4f104723e..a256f5e639 100644 --- a/src/sql/parts/modelComponents/tree/tree.component.ts +++ b/src/sql/parts/modelComponents/tree/tree.component.ts @@ -123,12 +123,11 @@ export default class TreeComponent extends ComponentBase implements IComponent, /// IComponent implementation public layout(): void { - this._changeRef.detectChanges(); if (this._tree) { - this.layoutTree(); this._tree.refresh(); } + super.layout(); } private layoutTree(): void { diff --git a/src/sql/parts/modelComponents/viewBase.ts b/src/sql/parts/modelComponents/viewBase.ts index ffb6dd0358..06be6c3b1c 100644 --- a/src/sql/parts/modelComponents/viewBase.ts +++ b/src/sql/parts/modelComponents/viewBase.ts @@ -71,15 +71,31 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { return descriptor; } + private removeComponent(component: IComponentShape): void { + if (component.itemConfigs) { + for (let item of component.itemConfigs) { + this.removeFromContainer(component.id, item); + } + } + } + clearContainer(componentId: string): void { this.queueAction(componentId, (component) => component.clearContainer()); } - addToContainer(containerId: string, itemConfig: IItemConfig): void { + addToContainer(containerId: string, itemConfig: IItemConfig, index?: number): void { // Do not return the promise as this should be non-blocking this.queueAction(containerId, (component) => { let childDescriptor = this.defineComponent(itemConfig.componentShape); - component.addToContainer(childDescriptor, itemConfig.config); + component.addToContainer(childDescriptor, itemConfig.config, index); + }); + } + + removeFromContainer(containerId: string, itemConfig: IItemConfig): void { + let childDescriptor = this.modelStore.getComponentDescriptor(itemConfig.componentShape.id); + this.queueAction(containerId, (component) => { + component.removeFromContainer(childDescriptor); + this.removeComponent(itemConfig.componentShape); }); } diff --git a/src/sql/services/model/modelViewService.ts b/src/sql/services/model/modelViewService.ts index 89ea327e28..2c71dd7eb6 100644 --- a/src/sql/services/model/modelViewService.ts +++ b/src/sql/services/model/modelViewService.ts @@ -22,7 +22,8 @@ export interface IModelViewEventArgs extends IComponentEventArgs { export interface IModelView extends IView { initializeModel(rootComponent: IComponentShape, validationCallback?: (componentId: string) => Thenable): void; clearContainer(componentId: string): void; - addToContainer(containerId: string, item: IItemConfig): void; + addToContainer(containerId: string, item: IItemConfig, index?: number): void; + removeFromContainer(containerId: string, item: IItemConfig): void; setLayout(componentId: string, layout: any): void; setProperties(componentId: string, properties: { [key: string]: any }): void; setDataProvider(handle: number, componentId: string, context: any): void; diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index c99be86edc..1428c91f46 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -119,6 +119,20 @@ declare module 'sqlops' { * @param {*} [itemLayout] Optional layout for this child item */ addFormItem(formComponent: FormComponent | FormComponentGroup, itemLayout?: FormItemLayout): void; + + /** + * Inserts a from component in a given position in the form. Returns error given invalid index + * @param formComponent Form component + * @param index index to insert the component to + * @param itemLayout Item Layout + */ + insertFormItem(formComponent: FormComponent | FormComponentGroup, index?: number, itemLayout?: FormItemLayout); + + /** + * Removes a from item from the from + * @param formComponent + */ + removeFormItem(formComponent: FormComponent | FormComponentGroup): boolean; } export interface Component { @@ -201,12 +215,28 @@ declare module 'sqlops' { /** * Creates a child component and adds it to this container. + * Adding component to multiple containers is not supported * * @param {Component} component the component to be added * @param {*} [itemLayout] Optional layout for this child item */ addItem(component: Component, itemLayout?: TItemLayout): void; + /** + * Creates a child component and inserts it to this container. Returns error given invalid index + * Adding component to multiple containers is not supported + * @param component the component to be added + * @param index the index to insert the component to + * @param {*} [itemLayout] Optional layout for this child item + */ + insertItem(component: Component, index: number, itemLayout?: TItemLayout): void; + + /** + * + * @param component Removes a component from this container + */ + removeItem(component: Component): boolean; + /** * Defines the layout for this container * diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index 0f7253651f..6559ee8a43 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -305,6 +305,15 @@ class FormContainerBuilder extends ContainerBuilderImpl { + let componentWrapper = component as ComponentWrapper; + this._component.removeItem(componentWrapper); + }); + } + } + addFormItems(formComponents: Array, itemLayout?: sqlops.FormItemLayout): void { formComponents.forEach(formComponent => { this.addFormItem(formComponent, itemLayout); @@ -312,25 +321,56 @@ class FormContainerBuilder extends ContainerBuilderImpl { let layout = component.layout || itemLayout; let itemConfig = this.convertToItemConfig(component, layout); itemConfig.config.isInGroup = true; - this._component.addItem(component.component as ComponentWrapper, itemConfig.config); + this._component.insertItem(component.component as ComponentWrapper, componentIndex, itemConfig.config); + if (componentIndex) { + componentIndex ++; + } this.addComponentActions(component, layout); }); } else { formComponent = formComponent as sqlops.FormComponent; let itemImpl = this.convertToItemConfig(formComponent, itemLayout); - this._component.addItem(formComponent.component as ComponentWrapper, itemImpl.config); + this._component.addItem(formComponent.component as ComponentWrapper, itemImpl.config, index); this.addComponentActions(formComponent, itemLayout); } } + + removeFormItem(formComponent: sqlops.FormComponent | sqlops.FormComponentGroup): boolean { + let componentGroup = formComponent as sqlops.FormComponentGroup; + let result: boolean = false; + if (componentGroup && componentGroup.components !== undefined) { + let firstComponent = componentGroup.components[0]; + let index = this._component.itemConfigs.findIndex(x => x.component.id === firstComponent.component.id); + if (index) { + result = this._component.removeItemAt(index - 1); + } + componentGroup.components.forEach(element => { + this.removeComponentActions(element); + this._component.removeItem(element.component); + }); + } else { + formComponent = formComponent as sqlops.FormComponent; + if (formComponent) { + result = this._component.removeItem(formComponent.component as ComponentWrapper); + this.removeComponentActions(formComponent); + } + } + return result; + } } class ToolbarContainerBuilder extends ContainerBuilderImpl implements sqlops.ToolbarBuilder { @@ -470,14 +510,42 @@ class ComponentWrapper implements sqlops.Component { } } - public addItem(item: sqlops.Component, itemLayout?: any): void { + public removeItemAt(index: number): boolean { + if (index >= 0 && index < this.itemConfigs.length) { + let itemConfig = this.itemConfigs[index]; + this._proxy.$removeFromContainer(this._handle, this.id, itemConfig.toIItemConfig()); + this.itemConfigs.splice(index, 1); + return true; + } + return false; + } + + public removeItem(item: sqlops.Component): boolean { + let index = this.itemConfigs.findIndex(c => c.component.id === item.id); + if (index >= 0 && index < this.itemConfigs.length) { + return this.removeItemAt(index); + } + return false; + } + + public insertItem(item: sqlops.Component, index: number, itemLayout?: any) { + this.addItem(item, itemLayout, index); + } + + public addItem(item: sqlops.Component, itemLayout?: any, index?: number): void { let itemImpl = item as ComponentWrapper; if (!itemImpl) { throw new Error(nls.localize('unknownComponentType', 'Unkown component type. Must use ModelBuilder to create objects')); } let config = new InternalItemConfig(itemImpl, itemLayout); - this.itemConfigs.push(config); - this._proxy.$addToContainer(this._handle, this.id, config.toIItemConfig()).then(undefined, this.handleError); + if (index !== undefined && index >= 0 && index < this.items.length) { + this.itemConfigs.splice(index, 0, config); + } else if (!index) { + this.itemConfigs.push(config); + } else { + throw new Error(nls.localize('invalidIndex', 'The index is invalid.')); + } + this._proxy.$addToContainer(this._handle, this.id, config.toIItemConfig(), index).then(undefined, this.handleError); } public setLayout(layout: any): Thenable { diff --git a/src/sql/workbench/api/node/mainThreadModelView.ts b/src/sql/workbench/api/node/mainThreadModelView.ts index 42191ebfe1..e010bbfc1d 100644 --- a/src/sql/workbench/api/node/mainThreadModelView.ts +++ b/src/sql/workbench/api/node/mainThreadModelView.ts @@ -53,9 +53,14 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi return this.execModelViewAction(handle, (modelView) => modelView.clearContainer(componentId)); } - $addToContainer(handle: number, containerId: string, item: IItemConfig): Thenable { + $addToContainer(handle: number, containerId: string, item: IItemConfig, index?: number): Thenable { return this.execModelViewAction(handle, - (modelView) => modelView.addToContainer(containerId, item)); + (modelView) => modelView.addToContainer(containerId, item, index)); + } + + $removeFromContainer(handle: number, containerId: string, item: IItemConfig): Thenable { + return this.execModelViewAction(handle, + (modelView) => modelView.removeFromContainer(containerId, item)); } $setLayout(handle: number, componentId: string, layout: any): Thenable { diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index d12bdb90b0..f954f6b8a7 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -634,7 +634,8 @@ export interface MainThreadModelViewShape extends IDisposable { $registerProvider(id: string): void; $initializeModel(handle: number, rootComponent: IComponentShape): Thenable; $clearContainer(handle: number, componentId: string): Thenable; - $addToContainer(handle: number, containerId: string, item: IItemConfig): Thenable; + $addToContainer(handle: number, containerId: string, item: IItemConfig, index?: number): Thenable; + $removeFromContainer(handle: number, containerId: string, item: IItemConfig): Thenable; $setLayout(handle: number, componentId: string, layout: any): Thenable; $setProperties(handle: number, componentId: string, properties: { [key: string]: any }): Thenable; $registerEvent(handle: number, componentId: string): Thenable; diff --git a/src/sqltest/parts/modelComponents/componentBase.test.ts b/src/sqltest/parts/modelComponents/componentBase.test.ts index 6ed7c8e773..05d6c0789a 100644 --- a/src/sqltest/parts/modelComponents/componentBase.test.ts +++ b/src/sqltest/parts/modelComponents/componentBase.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Mock, It, Times, MockBehavior } from 'typemoq'; -import { ComponentBase, ContainerBase } from 'sql/parts/modelComponents/componentBase'; +import { ComponentBase, ContainerBase, ItemDescriptor } from 'sql/parts/modelComponents/componentBase'; import { IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; import { ModelStore } from 'sql/parts/modelComponents/modelStore'; import { ChangeDetectorRef } from '@angular/core'; @@ -41,6 +41,10 @@ class TestContainer extends ContainerBase { this.baseInit(); } + public get TestItems(): ItemDescriptor[] { + return this.items; + } + ngOnInit() { } setLayout() { } @@ -51,12 +55,14 @@ class TestContainer extends ContainerBase { suite('ComponentBase Tests', () => { let testComponent: TestComponent; + let testComponent2: TestComponent; let testContainer: TestContainer; let modelStore: IModelStore; setup(() => { modelStore = new ModelStore(); testComponent = new TestComponent(modelStore, 'testComponent'); + testComponent2 = new TestComponent(modelStore, 'testComponent2'); testContainer = new TestContainer(modelStore, 'testContainer'); }); @@ -130,6 +136,63 @@ suite('ComponentBase Tests', () => { testComponent.validate(); }); + test('Inserting a component to a container adds the component to the right place', done => { + testContainer.addToContainer(testComponent.descriptor, undefined); + assert.equal(testContainer.TestItems.length, 1); + testContainer.addToContainer(testComponent2.descriptor, undefined, 0); + assert.equal(testContainer.TestItems.length, 2); + assert.equal(testContainer.TestItems[0].descriptor.id, testComponent2.descriptor.id); + done(); + }); + + test('Inserting a component to a container given negative index fails', done => { + testContainer.addToContainer(testComponent.descriptor, undefined); + assert.equal(testContainer.TestItems.length, 1); + assert.throws(() => testContainer.addToContainer(testComponent2.descriptor, undefined, -1)); + done(); + }); + + test('Inserting a component to a container given wrong index fails', done => { + testContainer.addToContainer(testComponent.descriptor, undefined); + assert.equal(testContainer.TestItems.length, 1); + assert.throws(() => testContainer.addToContainer(testComponent2.descriptor, undefined, 10)); + done(); + }); + + test('Inserting a component to a container given end of list fails', done => { + testContainer.addToContainer(testComponent.descriptor, undefined); + assert.equal(testContainer.TestItems.length, 1); + assert.throws(() => testContainer.addToContainer(testComponent2.descriptor, undefined, 1)); + done(); + }); + + test('Removing a component the does not exist does not make change in the items', done => { + testContainer.addToContainer(testComponent.descriptor, undefined); + assert.equal(testContainer.TestItems.length, 1); + testContainer.removeFromContainer(testComponent2.descriptor); + assert.equal(testContainer.TestItems.length, 1); + done(); + }); + + test('Removing a component removes it from items', done => { + testContainer.addToContainer(testComponent.descriptor, undefined); + testContainer.addToContainer(testComponent2.descriptor, undefined); + assert.equal(testContainer.TestItems.length, 2); + testContainer.removeFromContainer(testComponent.descriptor); + assert.equal(testContainer.TestItems.length, 1); + assert.equal(testContainer.TestItems[0].descriptor.id, testComponent2.descriptor.id); + done(); + }); + + test('Container dost not add same component twice', done => { + testContainer.addToContainer(testComponent.descriptor, undefined); + assert.equal(testContainer.TestItems.length, 1); + testContainer.addToContainer(testComponent.descriptor, 0); + assert.equal(testContainer.TestItems.length, 1); + done(); + }); + + test('Component convert size should add px', done => { let expected = '100px'; let actual = testComponent.convertSize(100); diff --git a/src/sqltest/workbench/api/extHostModelView.test.ts b/src/sqltest/workbench/api/extHostModelView.test.ts index d339ccedef..4cc3c1785b 100644 --- a/src/sqltest/workbench/api/extHostModelView.test.ts +++ b/src/sqltest/workbench/api/extHostModelView.test.ts @@ -14,6 +14,12 @@ import { IComponentShape, IItemConfig, ComponentEventType, IComponentEventArgs, import { TitledFormItemLayout } from 'sql/parts/modelComponents/formContainer.component'; 'use strict'; +interface InternalItemConfig { + toIItemConfig(): IItemConfig; +} +interface IWithItemConfig { + itemConfigs?: InternalItemConfig[]; +} suite('ExtHostModelView Validation Tests', () => { let extHostModelView: ExtHostModelView; @@ -33,6 +39,7 @@ suite('ExtHostModelView Validation Tests', () => { $initializeModel: (handle: number, rootComponent: IComponentShape) => undefined, $clearContainer: (handle: number, componentId: string) => undefined, $addToContainer: (handle: number, containerId: string, item: IItemConfig) => undefined, + $removeFromContainer: (handle: number, containerId: string, item: IItemConfig) => undefined, $setLayout: (handle: number, componentId: string, layout: any) => undefined, $setProperties: (handle: number, componentId: string, properties: { [key: string]: any }) => undefined, $registerEvent: (handle: number, componentId: string) => undefined, @@ -132,7 +139,7 @@ suite('ExtHostModelView Validation Tests', () => { }); test('Setting a form component as required initializes the model with the component required', () => { - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); // Set up the input component with required initially set to false let inputComponent = modelView.modelBuilder.inputBox().component(); @@ -157,7 +164,7 @@ suite('ExtHostModelView Validation Tests', () => { // Set up the mock proxy to save the component that gets initialized so that it can be verified let rootComponent: IComponentShape; mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); - mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); // Set up the form with a top level component and a group let topLevelList = modelView.modelBuilder.listBox().component(); @@ -213,4 +220,164 @@ suite('ExtHostModelView Validation Tests', () => { assert.equal((inputBoxConfig.config as sqlops.FormItemLayout).horizontal, groupInputLayout.horizontal); assert.equal((dropdownConfig.config as sqlops.FormItemLayout).horizontal, defaultLayout.horizontal); }); + + test('Inserting and removing components from a container should work correctly', () => { + // Set up the mock proxy to save the component that gets initialized so that it can be verified + let rootComponent: IComponentShape; + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); + + assert.equal((flex as IWithItemConfig).itemConfigs.length, 2); + flex.insertItem(dropDown, 1); + assert.equal((flex as IWithItemConfig).itemConfigs.length, 3); + assert.equal((flex as IWithItemConfig).itemConfigs[1].toIItemConfig().componentShape.type, ModelComponentTypes.DropDown); + flex.removeItem(listBox); + assert.equal((flex as IWithItemConfig).itemConfigs.length, 2); + assert.equal((flex as IWithItemConfig).itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.DropDown); + }); + + test('Inserting component give negative number fails', () => { + // Set up the mock proxy to save the component that gets initialized so that it can be verified + let rootComponent: IComponentShape; + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); + + assert.equal((flex as IWithItemConfig).itemConfigs.length, 2); + assert.throws(() => flex.insertItem(dropDown, -1)); + }); + + test('Inserting component give wrong number fails', () => { + // Set up the mock proxy to save the component that gets initialized so that it can be verified + let rootComponent: IComponentShape; + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); + + assert.equal((flex as IWithItemConfig).itemConfigs.length, 2); + assert.throws(() => flex.insertItem(dropDown, 10)); + }); + + test('Inserting component give end of the list fails', () => { + // Set up the mock proxy to save the component that gets initialized so that it can be verified + let rootComponent: IComponentShape; + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); + + assert.equal((flex as IWithItemConfig).itemConfigs.length, 2); + assert.throws(() => flex.insertItem(dropDown, 2)); + }); + + test('Removing a component that does not exist does not fail', () => { + // Set up the mock proxy to save the component that gets initialized so that it can be verified + let rootComponent: IComponentShape; + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + + let flex = modelView.modelBuilder.flexContainer().withItems([listBox, inputBox]).component(); + modelView.initializeModel(flex); + + let result = flex.removeItem(dropDown); + assert.equal(result, false); + assert.equal((flex as IWithItemConfig).itemConfigs.length, 2); + assert.equal((flex as IWithItemConfig).itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.ListBox); + }); + + + test('Inserting and removing component in a form should work correctly', () => { + // Set up the mock proxy to save the component that gets initialized so that it can be verified + let rootComponent: IComponentShape; + mockProxy.setup(x => x.$initializeModel(It.isAny(), It.isAny())).callback((handle, componentShape) => rootComponent = componentShape); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), undefined)).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$addToContainer(It.isAny(), It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + mockProxy.setup(x => x.$removeFromContainer(It.isAny(), It.isAny(), It.isAny())).returns(() => Promise.resolve()); + + // Set up the form with a top level component and a group + let listBox = modelView.modelBuilder.listBox().component(); + let inputBox = modelView.modelBuilder.inputBox().component(); + let dropDown = modelView.modelBuilder.dropDown().component(); + let checkBox = modelView.modelBuilder.checkBox().component(); + + let groupItems: sqlops.FormComponentGroup = { + title: 'Group', + components: [{ + title: 'Drop Down', + component: dropDown + }, { + title: 'Check Box', + component: checkBox + }] + }; + let listBoxFormItem: sqlops.FormComponent = { + title: 'List Box', + component: listBox + }; + let inputBoxFormItem: sqlops.FormComponent = { + title: 'Input Box', + component: inputBox + }; + + let formBuilder = modelView.modelBuilder.formContainer(); + formBuilder.addFormItem(listBoxFormItem); + let form = formBuilder.component(); + modelView.initializeModel(formBuilder.component()); + + assert.equal((form as IWithItemConfig).itemConfigs.length, 1); + formBuilder.insertFormItem(inputBoxFormItem, 0); + assert.equal((form as IWithItemConfig).itemConfigs.length, 2); + assert.equal((form as IWithItemConfig).itemConfigs[0].toIItemConfig().componentShape.type, ModelComponentTypes.InputBox); + formBuilder.insertFormItem(groupItems, 0); + assert.equal((form as IWithItemConfig).itemConfigs.length, 5); + formBuilder.removeFormItem(listBoxFormItem); + assert.equal((form as IWithItemConfig).itemConfigs.length, 4); + formBuilder.removeFormItem(groupItems); + assert.equal((form as IWithItemConfig).itemConfigs.length, 1); + formBuilder.addFormItem(listBoxFormItem); + assert.equal((form as IWithItemConfig).itemConfigs.length, 2); + assert.equal((form as IWithItemConfig).itemConfigs[1].toIItemConfig().componentShape.type, ModelComponentTypes.ListBox); + }); }); \ No newline at end of file