From 110eef505b62ea3a997b86c258b80847c4dd3bad Mon Sep 17 00:00:00 2001 From: "S. Chris Colbert" Date: Tue, 11 Jul 2017 14:47:12 -0500 Subject: [PATCH 1/2] add a RenderWidget --- packages/virtualdom/src/index.ts | 11 +- packages/widgets/src/index.ts | 1 + packages/widgets/src/renderwidget.ts | 154 +++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 packages/widgets/src/renderwidget.ts diff --git a/packages/virtualdom/src/index.ts b/packages/virtualdom/src/index.ts index 43a5bb37b..f6ccb0dda 100644 --- a/packages/virtualdom/src/index.ts +++ b/packages/virtualdom/src/index.ts @@ -755,6 +755,13 @@ export type VirtualNode = VirtualElement | VirtualText; +/** + * A type alias for virtual content. + */ +export +type VirtualContent = VirtualNode | ReadonlyArray | null; + + /** * Create a new virtual element node. * @@ -972,7 +979,7 @@ namespace VirtualDOM { * result in undefined rendering behavior. */ export - function render(content: VirtualNode | ReadonlyArray | null, host: HTMLElement): void { + function render(content: VirtualContent, host: HTMLElement): void { let oldContent = Private.hostMap.get(host) || []; let newContent = Private.asContentArray(content); Private.hostMap.set(host, newContent); @@ -995,7 +1002,7 @@ namespace Private { * Cast a content value to a content array. */ export - function asContentArray(value: VirtualNode | ReadonlyArray | null): ReadonlyArray { + function asContentArray(value: VirtualContent): ReadonlyArray { if (!value) { return []; } diff --git a/packages/widgets/src/index.ts b/packages/widgets/src/index.ts index 7f80e13ff..a6a8d5cc2 100644 --- a/packages/widgets/src/index.ts +++ b/packages/widgets/src/index.ts @@ -19,6 +19,7 @@ export * from './menu'; export * from './menubar'; export * from './panel'; export * from './panellayout'; +export * from './renderwidget'; export * from './scrollbar'; export * from './singlelayout'; export * from './splitlayout'; diff --git a/packages/widgets/src/renderwidget.ts b/packages/widgets/src/renderwidget.ts new file mode 100644 index 000000000..3aeb39421 --- /dev/null +++ b/packages/widgets/src/renderwidget.ts @@ -0,0 +1,154 @@ +/*----------------------------------------------------------------------------- +| Copyright (c) 2014-2017, PhosphorJS Contributors +| +| Distributed under the terms of the BSD 3-Clause License. +| +| The full license is in the file LICENSE, distributed with this software. +|----------------------------------------------------------------------------*/ +import { + Message, MessageLoop +} from '@phosphor/messaging'; + +import { + VirtualContent, VirtualDOM +} from '@phosphor/virtualdom'; + +import { + Widget +} from './widget'; + + +/** + * A widget which renders its content using the virtual DOM. + */ +export +abstract class RenderWidget extends Widget { + /** + * Construct a new render widget. + */ + constructor() { + super(); + this.addClass('p-RenderWidget'); + this.setFlag(Widget.Flag.DisallowLayout); + } + + /** + * Process a message sent to the widget. + */ + processMessage(msg: Message): void { + switch (msg.type) { + case 'before-render': + this.onBeforeRender(msg); + break; + case 'after-render': + this.onAfterRender(msg); + break; + default: + super.processMessage(msg); + } + } + + /** + * Create the virtual DOM content for the widget. + * + * @returns The virtual DOM content to render into the widget. + * + * #### Notes + * This method is called automatically after the widget is attached + * or made visible. It can be triggered procedurally by calling the + * `update()` method. + * + * This will not be invoked if `shouldRender()` returns `false`. + * + * This method must be implemented by a subclass. + */ + protected abstract render(): VirtualContent; + + /** + * Test whether the widget should be rendered. + * + * @returns Whether the widget content should be rendered. + * + * #### Notes + * This method is invoked when the widget receives a message of type + * `'update-request'`. It is used to determine whether to (re)render + * the widget content. If this method returns `false`, the `render` + * method will not be invoked and the widget will not be updated. + * + * The default implementation of this method returns `true` IFF the + * widget is visible. + * + * A subclass may reimplement this method as needed. + */ + protected shouldRender(): boolean { + return this.isVisible; + } + + /** + * A message handler invoked on a `'before-render'` message. + * + * #### Notes + * The default implementation of this method is a no-op. + */ + protected onBeforeRender(msg: Message): void { } + + /** + * A message handler invoked on an `'after-render'` message. + * + * #### Notes + * The default implementation of this method is a no-op. + */ + protected onAfterRender(msg: Message): void { } + + /** + * A message handler invoked on a `'before-attach'` message. + */ + protected onBeforeAttach(msg: Message): void { + this.update(); + } + + /** + * A message handler invoked on a `'before-show'` message. + */ + protected onBeforeShow(msg: Message): void { + this.update(); + } + + /** + * A message handler invoked on an `'update-request'` message. + */ + protected onUpdateRequest(msg: Message): void { + // Bail if the widget should not render. + if (!this.shouldRender()) { + return; + } + + // Send a `'before-render'` message to the widget. + MessageLoop.sendMessage(this, RenderWidget.BeforeRender); + + // Render the virtual content into the widget. + VirtualDOM.render(this.render(), this.node); + + // Send an `'after-render'` message to the widget. + MessageLoop.sendMessage(this, RenderWidget.AfterRender); + } +} + + +/** + * The namespace for the `RenderWidget` class statics. + */ +export +namespace RenderWidget { + /** + * A singleton `'before-render'` message. + */ + export + const BeforeRender = new Message('before-render'); + + /** + * A singleton `'after-render'` message. + */ + export + const AfterRender = new Message('after-render'); +} From afdd6844a47eba537deaa53e92c6d8118b21d9aa Mon Sep 17 00:00:00 2001 From: "S. Chris Colbert" Date: Tue, 11 Jul 2017 17:21:18 -0500 Subject: [PATCH 2/2] add a model concept to render widget --- packages/widgets/src/renderwidget.ts | 73 +++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/packages/widgets/src/renderwidget.ts b/packages/widgets/src/renderwidget.ts index 3aeb39421..073f88eb7 100644 --- a/packages/widgets/src/renderwidget.ts +++ b/packages/widgets/src/renderwidget.ts @@ -9,6 +9,10 @@ import { Message, MessageLoop } from '@phosphor/messaging'; +import { + ISignal +} from '@phosphor/signaling'; + import { VirtualContent, VirtualDOM } from '@phosphor/virtualdom'; @@ -18,11 +22,31 @@ import { } from './widget'; +/** + * An object which can be used as a model for a render widget. + */ +export +interface IRenderModel { + /** + * A signal emitted when the model state has changed. + * + * #### notes + * If this signal is provided, the render widget will automatically + * update whenever the signal is emitted. + */ + readonly stateChanged?: ISignal; +} + + /** * A widget which renders its content using the virtual DOM. + * + * #### Notes + * Most subclasses will typically only implement the abstract `render()` + * method. Advanced use cases may reimplement some of the other methods. */ export -abstract class RenderWidget extends Widget { +abstract class RenderWidget extends Widget { /** * Construct a new render widget. */ @@ -32,6 +56,39 @@ abstract class RenderWidget extends Widget { this.setFlag(Widget.Flag.DisallowLayout); } + /** + * Get the model for the widget. + */ + get model(): T | null { + return this._model; + } + + /** + * Set the model for the widget. + */ + set model(value: T | null) { + // Bail early if the model does not change. + if (this._model === value) { + return; + } + + // Disconnect from the `stateChanged` signal, if provided. + if (this._model && this._model.stateChanged) { + this._model.stateChanged.disconnect(this.onModelStateChanged, this); + } + + // Update the internal model + this._model = value; + + // Connect to the `stateChanged` signal, if provided. + if (this._model && this._model.stateChanged) { + this._model.stateChanged.connect(this.onModelStateChanged, this); + } + + // Schedule an update of the widget. + this.update(); + } + /** * Process a message sent to the widget. */ @@ -132,6 +189,20 @@ abstract class RenderWidget extends Widget { // Send an `'after-render'` message to the widget. MessageLoop.sendMessage(this, RenderWidget.AfterRender); } + + /** + * Handle the `stateChanged` signal from the model. + * + * #### Notes + * The default implementation schedules an update of the widget. + * + * A subclass may reimplement this method as needed. + */ + protected onModelStateChanged(): void { + this.update(); + } + + private _model: T | null = null; }