model view remove and insert components (#2351)

* added the ability to remove a component from a container to insert it to a position
This commit is contained in:
Leila Lali
2018-08-31 13:08:27 -07:00
committed by GitHub
parent 8e0c19fc8d
commit b27f69aace
25 changed files with 453 additions and 94 deletions

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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<IComponentEventArgs>();
public layout(): void {
this._changeRef.detectChanges();
if (!this._changeRef['destroyed']) {
this._changeRef.detectChanges();
}
}
protected baseInit(): void {
@@ -222,17 +225,34 @@ export abstract class ContainerBase<T> 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 {

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -152,7 +152,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
/// IComponent implementation
public layout(): void {
this._changeRef.detectChanges();
super.layout();
this.layoutInputBox();
}

View File

@@ -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;

View File

@@ -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();

View File

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

View File

@@ -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
}

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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 {

View File

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

View File

@@ -22,7 +22,8 @@ export interface IModelViewEventArgs extends IComponentEventArgs {
export interface IModelView extends IView {
initializeModel(rootComponent: IComponentShape, validationCallback?: (componentId: string) => Thenable<boolean>): 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;

View File

@@ -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
*

View File

@@ -305,6 +305,15 @@ class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sq
}
}
private removeComponentActions(formComponent: sqlops.FormComponent): void {
if (formComponent.actions) {
formComponent.actions.forEach(component => {
let componentWrapper = component as ComponentWrapper;
this._component.removeItem(componentWrapper);
});
}
}
addFormItems(formComponents: Array<sqlops.FormComponent | sqlops.FormComponentGroup>, itemLayout?: sqlops.FormItemLayout): void {
formComponents.forEach(formComponent => {
this.addFormItem(formComponent, itemLayout);
@@ -312,25 +321,56 @@ class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sq
}
addFormItem(formComponent: sqlops.FormComponent | sqlops.FormComponentGroup, itemLayout?: sqlops.FormItemLayout): void {
this.insertFormItem(formComponent, undefined, itemLayout);
}
insertFormItem(formComponent: sqlops.FormComponent | sqlops.FormComponentGroup, index?: number, itemLayout?: sqlops.FormItemLayout): void {
let componentGroup = formComponent as sqlops.FormComponentGroup;
if (componentGroup && componentGroup.components !== undefined) {
let labelComponent = this._builder.text().component();
labelComponent.value = componentGroup.title;
this._component.addItem(labelComponent, { isGroupLabel: true });
this._component.addItem(labelComponent, { isGroupLabel: true }, index);
let componentIndex = index ? index + 1 : undefined;
componentGroup.components.forEach(component => {
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<sqlops.ToolbarContainer, sqlops.ToolbarLayout, any> 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<void> {

View File

@@ -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<void> {
$addToContainer(handle: number, containerId: string, item: IItemConfig, index?: number): Thenable<void> {
return this.execModelViewAction(handle,
(modelView) => modelView.addToContainer(containerId, item));
(modelView) => modelView.addToContainer(containerId, item, index));
}
$removeFromContainer(handle: number, containerId: string, item: IItemConfig): Thenable<void> {
return this.execModelViewAction(handle,
(modelView) => modelView.removeFromContainer(containerId, item));
}
$setLayout(handle: number, componentId: string, layout: any): Thenable<void> {

View File

@@ -634,7 +634,8 @@ export interface MainThreadModelViewShape extends IDisposable {
$registerProvider(id: string): void;
$initializeModel(handle: number, rootComponent: IComponentShape): Thenable<void>;
$clearContainer(handle: number, componentId: string): Thenable<void>;
$addToContainer(handle: number, containerId: string, item: IItemConfig): Thenable<void>;
$addToContainer(handle: number, containerId: string, item: IItemConfig, index?: number): Thenable<void>;
$removeFromContainer(handle: number, containerId: string, item: IItemConfig): Thenable<void>;
$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>;