diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts index f1eb306e0b..448f443aa9 100644 --- a/samples/sqlservices/src/controllers/mainController.ts +++ b/samples/sqlservices/src/controllers/mainController.ts @@ -72,9 +72,6 @@ export default class MainController implements vscode.Disposable { let inputBox2 = view.modelBuilder.inputBox() .component(); - let inputBox3 = view.modelBuilder.inputBox() - .component(); - let checkbox = view.modelBuilder.checkBox() .withProperties({ label: 'Copy-only backup' @@ -114,6 +111,31 @@ export default class MainController implements vscode.Disposable { vscode.window.showInformationMessage(inputBox2.value); inputBox.value = dropdown.value; }); + let radioButton = view.modelBuilder.radioButton() + .withProperties({ + value: 'option1', + name: 'radioButtonOptions', + label: 'Option 1', + checked: true + //width: 300 + }).component(); + let radioButton2 = view.modelBuilder.radioButton() + .withProperties({ + value: 'option2', + name: 'radioButtonOptions', + label: 'Option 2' + + //width: 300 + }).component(); + let flexRadioButtonsModel = view.modelBuilder.flexContainer() + .withLayout({ + flexFlow: 'column', + alignItems: 'left', + justifyContent: 'space-evenly', + height: 50 + }).withItems([ + radioButton, radioButton2] + , { flex: '1 1 50%' }).component(); let formModel = view.modelBuilder.formContainer() .withFormItems([{ component: inputBox, @@ -131,6 +153,9 @@ export default class MainController implements vscode.Disposable { component: inputBox2, title: 'Backup files', actions: [button, button3] + }, { + component: flexRadioButtonsModel, + title: 'Options' }], { horizontal:false, width: 500, diff --git a/src/sql/base/browser/ui/radioButton/radioButton.ts b/src/sql/base/browser/ui/radioButton/radioButton.ts new file mode 100644 index 0000000000..3324f0b620 --- /dev/null +++ b/src/sql/base/browser/ui/radioButton/radioButton.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +import * as DOM from 'vs/base/browser/dom'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { Color } from 'vs/base/common/color'; +import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import Event, { Emitter } from 'vs/base/common/event'; +import { Widget } from 'vs/base/browser/ui/widget'; + +export interface IRadioButtonOptions { + label: string; + enabled?: boolean; + checked?: boolean; +} + +export class RadioButton extends Widget { + + private inputElement: HTMLInputElement; + private _onClicked = new Emitter(); + public readonly onClicked: Event = this._onClicked.event; + private _label: HTMLSpanElement; + + constructor(container: HTMLElement, opts: IRadioButtonOptions) { + super(); + this.inputElement = document.createElement('input'); + this.inputElement.type = 'radio'; + + this._label = document.createElement('span'); + + this.label = opts.label; + this.enabled = opts.enabled || true; + this.checked = opts.checked || false; + this.onclick(this.inputElement, () => this._onClicked.fire()); + + container.appendChild(this.inputElement); + container.appendChild(this._label); + } + + public set name(value: string) { + this.inputElement.setAttribute('name', value); + } + + public get name(): string { + return this.inputElement.getAttribute('name'); + } + + public set value(value: string) { + this.inputElement.setAttribute('value', value); + } + + public get value(): string { + return this.inputElement.getAttribute('value'); + } + + public set checked(val: boolean) { + this.inputElement.checked = val; + } + + public get checked(): boolean { + return this.inputElement.checked; + } + + public set enabled(val: boolean) { + this.inputElement.disabled = !val; + } + + public get enabled(): boolean { + return !this.inputElement.disabled; + } + + public isEnabled(): boolean { + return !this.inputElement.hasAttribute('disabled'); + } + + public set label(val: string) { + this._label.innerText = val; + } + +} \ No newline at end of file diff --git a/src/sql/parts/modelComponents/checkbox.component.ts b/src/sql/parts/modelComponents/checkbox.component.ts index d04dea5258..362eaf333b 100644 --- a/src/sql/parts/modelComponents/checkbox.component.ts +++ b/src/sql/parts/modelComponents/checkbox.component.ts @@ -78,6 +78,11 @@ export default class CheckBoxComponent extends ComponentBase implements ICompone super.setProperties(properties); this._input.checked = this.checked; this._input.label = this.label; + if (this.enabled) { + this._input.enable(); + } else { + this._input.disable(); + } } // CSS-bound properties @@ -87,11 +92,7 @@ export default class CheckBoxComponent extends ComponentBase implements ICompone } public set value(newValue: boolean) { - this.setPropertyFromUI(this.setInputBoxProperties, newValue); - } - - private setInputBoxProperties(properties: sqlops.CheckBoxProperties, value: boolean): void { - properties.checked = value; + this.setPropertyFromUI((properties, value) => { properties.checked = value; }, newValue); } private get label(): string { @@ -99,10 +100,6 @@ export default class CheckBoxComponent extends ComponentBase implements ICompone } private set label(newValue: string) { - this.setPropertyFromUI(this.setValueProperties, newValue); - } - - private setValueProperties(properties: sqlops.CheckBoxProperties, label: string): void { - properties.label = label; + this.setPropertyFromUI((properties, label) => { properties.label = label; }, newValue); } } diff --git a/src/sql/parts/modelComponents/componentBase.ts b/src/sql/parts/modelComponents/componentBase.ts index ac10f3d171..f89e8fe182 100644 --- a/src/sql/parts/modelComponents/componentBase.ts +++ b/src/sql/parts/modelComponents/componentBase.ts @@ -94,7 +94,15 @@ export abstract class ComponentBase extends Disposable implements IComponent, On public get enabled(): boolean { let properties = this.getProperties(); let enabled = properties['enabled']; - return enabled !== undefined ? enabled : true; + if (enabled === undefined) { + enabled = true; + properties['enabled'] = enabled; + this.fireEvent({ + eventType: ComponentEventType.PropertiesChanged, + args: this.getProperties() + }); + } + return enabled; } public get valid(): boolean { diff --git a/src/sql/parts/modelComponents/components.contribution.ts b/src/sql/parts/modelComponents/components.contribution.ts index 7df5acd9e2..2403ca7d2f 100644 --- a/src/sql/parts/modelComponents/components.contribution.ts +++ b/src/sql/parts/modelComponents/components.contribution.ts @@ -10,6 +10,7 @@ import InputBoxComponent from './inputbox.component'; import DropDownComponent from './dropdown.component'; import ButtonComponent from './button.component'; import CheckBoxComponent from './checkbox.component'; +import RadioButtonComponent from './radioButton.component'; import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry'; import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes'; @@ -34,3 +35,6 @@ registerComponentType(BUTTON_COMPONENT, ModelComponentTypes.Button, ButtonCompon export const CHECKBOX_COMPONENT = 'checkbox-component'; registerComponentType(CHECKBOX_COMPONENT, ModelComponentTypes.CheckBox, CheckBoxComponent); + +export const RADIOBUTTON_COMPONENT = 'radiobutton-component'; +registerComponentType(RADIOBUTTON_COMPONENT, ModelComponentTypes.RadioButton, RadioButtonComponent); diff --git a/src/sql/parts/modelComponents/dropdown.component.ts b/src/sql/parts/modelComponents/dropdown.component.ts index 00d613e0ae..a78f607126 100644 --- a/src/sql/parts/modelComponents/dropdown.component.ts +++ b/src/sql/parts/modelComponents/dropdown.component.ts @@ -87,6 +87,7 @@ export default class DropDownComponent extends ComponentBase implements ICompone if (this.value) { this._dropdown.value = this.value; } + this._dropdown.enabled = this.enabled; } // CSS-bound properties diff --git a/src/sql/parts/modelComponents/flexContainer.component.ts b/src/sql/parts/modelComponents/flexContainer.component.ts index 385640bd16..b51893a8bd 100644 --- a/src/sql/parts/modelComponents/flexContainer.component.ts +++ b/src/sql/parts/modelComponents/flexContainer.component.ts @@ -22,7 +22,7 @@ class FlexItem { @Component({ template: `
+ [style.alignItems]="alignItems" [style.alignContent]="alignContent" [style.height]="height">
@@ -37,6 +37,7 @@ export default class FlexContainer extends ContainerBase impleme private _justifyContent: string; private _alignItems: string; private _alignContent: string; + private _height: string; @ViewChildren(ModelComponentWrapper) private _componentWrappers: QueryList; @@ -70,6 +71,7 @@ export default class FlexContainer extends ContainerBase impleme this._justifyContent= layout.justifyContent ? layout.justifyContent : ''; this._alignItems= layout.alignItems ? layout.alignItems : ''; this._alignContent= layout.alignContent ? layout.alignContent : ''; + this._height= layout.height ? layout.height + 'px' : ''; this.layout(); } @@ -86,6 +88,10 @@ export default class FlexContainer extends ContainerBase impleme return this._alignItems; } + public get height(): string { + return this._height; + } + public get alignContent(): string { return this._alignContent; } diff --git a/src/sql/parts/modelComponents/radioButton.component.ts b/src/sql/parts/modelComponents/radioButton.component.ts new file mode 100644 index 0000000000..2030cb9329 --- /dev/null +++ b/src/sql/parts/modelComponents/radioButton.component.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./radioButton'; +import { + Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, + ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit +} from '@angular/core'; + +import * as sqlops from 'sqlops'; +import Event, { Emitter } from 'vs/base/common/event'; + +import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; +import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; +import { RadioButton } from 'sql/base/browser/ui/radioButton/radioButton'; +import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; + +@Component({ + selector: 'radioButton', + template: ` +
+ +
+ ` +}) +export default class RadioButtonComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit { + @Input() descriptor: IComponentDescriptor; + @Input() modelStore: IModelStore; + private _input: RadioButton; + + @ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef; + constructor( + @Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface, + @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) { + super(changeRef); + } + + ngOnInit(): void { + this.baseInit(); + + } + + ngAfterViewInit(): void { + if (this._inputContainer) { + this._input = new RadioButton(this._inputContainer.nativeElement, { + label: this.label + }); + + this._register(this._input); + this._register(this._input.onClicked(e => { + this._onEventEmitter.fire({ + eventType: ComponentEventType.onDidClick, + args: e + }); + })); + } + } + + ngOnDestroy(): void { + this.baseDestroy(); + } + + /// IComponent implementation + + public layout(): void { + this._changeRef.detectChanges(); + } + + public setLayout(layout: any): void { + // TODO allow configuring the look and feel + this.layout(); + } + + public setProperties(properties: { [key: string]: any; }): void { + super.setProperties(properties); + this._input.name = this.name; + this._input.value = this.value; + this._input.label = this.label; + this._input.enabled = this.enabled; + + this._input.checked = this.checked; + } + + // CSS-bound properties + + public get checked(): boolean { + return this.getPropertyOrDefault((props) => props.checked, false); + } + + public set value(newValue: string) { + this.setPropertyFromUI((properties, value) => { properties.checked = value; }, newValue); + } + + public get value(): string { + return this.getPropertyOrDefault((props) => props.value, ''); + } + + public getLabel(): string { + return this.label; + } + + public get label(): string { + return this.getPropertyOrDefault((props) => props.label, ''); + } + + public set label(newValue: string) { + this.setPropertyFromUI((properties, label) => { properties.label = label; }, newValue); + } + + public get name(): string { + return this.getPropertyOrDefault((props) => props.name, ''); + } + + public set name(newValue: string) { + this.setPropertyFromUI((properties, label) => { properties.name = label; }, newValue); + } +} diff --git a/src/sql/parts/modelComponents/radioButton.css b/src/sql/parts/modelComponents/radioButton.css new file mode 100644 index 0000000000..249838444d --- /dev/null +++ b/src/sql/parts/modelComponents/radioButton.css @@ -0,0 +1,11 @@ +.modelview-radiobutton-container { + align-items: flex-start; +} + +.modelview-radiobutton-item { + align-self: flex-start ; +} + +.modelview-radiobutton-title { + +} \ No newline at end of file diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index bfcd0db951..10ec80c65c 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -21,6 +21,7 @@ declare module 'sqlops' { card(): ComponentBuilder; inputBox(): ComponentBuilder; checkBox(): ComponentBuilder; + radioButton(): ComponentBuilder; button(): ComponentBuilder; dropDown(): ComponentBuilder; dashboardWidget(widgetId: string): ComponentBuilder; @@ -44,6 +45,22 @@ declare module 'sqlops' { export interface FormBuilder extends ContainerBuilder { withFormItems(components: FormComponent[], itemLayout?: FormItemLayout): ContainerBuilder; + + /** + * Creates a collection of child components and adds them all to this container + * + * @param formComponents the definitions + * @param {*} [itemLayout] Optional layout for the child items + */ + addFormItems(formComponents: Array, itemLayout?: FormItemLayout): void; + + /** + * Creates a child component and adds it to this container. + * + * @param formComponent the component to be added + * @param {*} [itemLayout] Optional layout for this child item + */ + addFormItem(formComponent: FormComponent, itemLayout?: FormItemLayout): void; } export interface Component { @@ -147,6 +164,8 @@ declare module 'sqlops' { * Matches the align-content CSS property. */ alignContent?: string; + + height? : number; } export interface FlexItemLayout { @@ -236,6 +255,13 @@ declare module 'sqlops' { label?: string; } + export interface RadioButtonProperties { + name?: string; + label?: string; + value?: string; + checked?: boolean; + } + export interface DropDownProperties { value?: string; values?: string[]; @@ -256,6 +282,10 @@ declare module 'sqlops' { onTextChanged: vscode.Event; } + export interface RadioButtonComponent extends Component, RadioButtonProperties { + onDidClick: vscode.Event; + } + export interface CheckBoxComponent extends Component { checked: boolean; label: string; diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 3df02433b6..918a2eda2b 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -71,6 +71,7 @@ export enum ModelComponentTypes { DropDown, Button, CheckBox, + RadioButton, DashboardWidget, DashboardWebview, Form diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index 7efc30a379..f0657ece46 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -59,6 +59,13 @@ class ModelBuilderImpl implements sqlops.ModelBuilder { return builder; } + radioButton(): sqlops.ComponentBuilder { + let id = this.getNextComponentId(); + let builder: ComponentBuilderImpl = this.getComponentBuilder(new RadioButtonWrapper(this._proxy, this._handle, id), id); + this._componentBuilders.set(id, builder); + return builder; + } + checkBox(): sqlops.ComponentBuilder { let id = this.getNextComponentId(); let builder: ComponentBuilderImpl = this.getComponentBuilder(new CheckBoxWrapper(this._proxy, this._handle, id), id); @@ -177,36 +184,56 @@ class ContainerBuilderImpl ext } } -class FormContainerBuilder extends ContainerBuilderImpl { +class FormContainerBuilder extends ContainerBuilderImpl implements sqlops.FormBuilder { withFormItems(components: sqlops.FormComponent[], itemLayout?: sqlops.FormItemLayout): sqlops.ContainerBuilder { - this._component.itemConfigs = components.map(item => { - let componentWrapper = item.component as ComponentWrapper; - let actions: string[] = undefined; - if (item.actions) { - actions = item.actions.map(action => { - let actionComponentWrapper = action as ComponentWrapper; - return actionComponentWrapper.id; - }); - } - return new InternalItemConfig(componentWrapper, Object.assign({}, itemLayout, { - title: item.title, - actions: actions, - isFormComponent: true - })); + return this.convertToItemConfig(item, itemLayout); }); components.forEach(formItem => { - if (formItem.actions) { - formItem.actions.forEach(component => { - let componentWrapper = component as ComponentWrapper; - this._component.itemConfigs.push(new InternalItemConfig(componentWrapper, itemLayout)); - }); - } + this.addComponentActions(formItem, itemLayout); }); return this; } + + private convertToItemConfig(formComponent: sqlops.FormComponent, itemLayout?: sqlops.FormItemLayout): InternalItemConfig { + let componentWrapper = formComponent.component as ComponentWrapper; + let actions: string[] = undefined; + if (formComponent.actions) { + actions = formComponent.actions.map(action => { + let actionComponentWrapper = action as ComponentWrapper; + return actionComponentWrapper.id; + }); + } + + return new InternalItemConfig(componentWrapper, Object.assign({}, itemLayout, { + title: formComponent.title, + actions: actions, + isFormComponent: true + })); + } + + private addComponentActions(formComponent: sqlops.FormComponent, itemLayout?: sqlops.FormItemLayout): void { + if (formComponent.actions) { + formComponent.actions.forEach(component => { + let componentWrapper = component as ComponentWrapper; + this._component.addItem(componentWrapper, itemLayout); + }); + } + } + + addFormItems(formComponents: Array, itemLayout?: sqlops.FormItemLayout): void { + formComponents.forEach(formComponent => { + this.addFormItem(formComponent, itemLayout); + }); + } + + addFormItem(formComponent: sqlops.FormComponent, itemLayout?: sqlops.FormItemLayout): void { + let itemImpl = this.convertToItemConfig(formComponent, itemLayout); + this._component.addItem(formComponent.component as ComponentWrapper, itemImpl.config); + this.addComponentActions(formComponent, itemLayout); + } } class InternalItemConfig { @@ -489,6 +516,47 @@ class CheckBoxWrapper extends ComponentWrapper implements sqlops.CheckBoxCompone } } +class RadioButtonWrapper extends ComponentWrapper implements sqlops.RadioButtonComponent { + + constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { + super(proxy, handle, ModelComponentTypes.RadioButton, id); + this.properties = {}; + this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); + } + + public get name(): string { + return this.properties['name']; + } + public set name(v: string) { + this.setProperty('name', v); + } + + public get label(): string { + return this.properties['label']; + } + public set label(v: string) { + this.setProperty('label', v); + } + + public get value(): string { + return this.properties['value']; + } + public set value(v: string) { + this.setProperty('value', v); + } + public get checked(): boolean { + return this.properties['checked']; + } + public set checked(v: boolean) { + this.setProperty('checked', v); + } + + public get onDidClick(): vscode.Event { + let emitter = this._emitterMap.get(ComponentEventType.onDidClick); + return emitter && emitter.event; + } +} + class DropDownWrapper extends ComponentWrapper implements sqlops.DropDownComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {