This repository has been archived by the owner on Nov 6, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 168
add a RenderWidget #299
Open
sccolbert
wants to merge
2
commits into
master
Choose a base branch
from
feature-render-widget
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
add a RenderWidget #299
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
/*----------------------------------------------------------------------------- | ||
| 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 { | ||
ISignal | ||
} from '@phosphor/signaling'; | ||
|
||
import { | ||
VirtualContent, VirtualDOM | ||
} from '@phosphor/virtualdom'; | ||
|
||
import { | ||
Widget | ||
} 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<this, void>; | ||
} | ||
|
||
|
||
/** | ||
* 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<T extends IRenderModel = {}> extends Widget { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In JLab we have stopped using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been trying to keep widget names to two words, so |
||
/** | ||
* Construct a new render widget. | ||
*/ | ||
constructor() { | ||
super(); | ||
this.addClass('p-RenderWidget'); | ||
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. | ||
*/ | ||
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); | ||
} | ||
|
||
/** | ||
* 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; | ||
} | ||
|
||
|
||
/** | ||
* 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'); | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this was the main point I wanted to make - that this should be a simple interface with only the signal. This will allow us to use it with our Redux style state stores as well. Is the name of the signal the same in both places?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name is not the same in both places, but your model is likely to wrap the store anyway.
We were talking about a few alternatives:
T | null
and have aprotected modelSwapped(old, new)
method which gets called, and let the subclass subscribe/unsubscribe as/if needed to their model type.