Link to recorded videos: https://drive.google.com/drive/folders/1tfJ53NHQyequSHbYbSvs3jIFuWbcYZ9E?usp=sharing
Meeting: https://zoom.us/j/650076676
Covered Packages: algorithm collections messaging signaling disposable properties coreutils domutils keyboard commands dragdrop virtualdom
Remaining Packages: widgets application
In-development Packages: datagrid datastore
Scrollbar
- A lot of virtual scrolling implementations use a large div to create a system scrollbar. Some system scrollbars have limitations for how many items they can scroll, like a million pixels or something. For large data sets (like a trillion rows), we still need custom scrollbars.
- Also allows styling, which has been notoriously difficult with system scrollbars
- Used in other Widgets like DataGrid
- Simple interface. Minimum value is 0. Maximum is settable. Page size is how many pixels to skip
- clicking and dragging the thumb, clicking on step (arrow) buttons and clicking on track do different things
- dragging the thumb emits the
thumbMoved
signal - clicking on a step button emits a
setupRequested
signal - clicking on scroll track emits a
pageRequested
signal
- dragging the thumb emits the
- clicking and holding on track or step button will emit the related signal repeatedly
- can style scrollbar based on
data-orientation
DOM field (“horizontal” vs “vertical") - We make sure to release the mouse holds after we detach from the DOM
- onUpdateRequest - first tried to convert everything to pixels, but it was complicated and didn’t render well. So now we work entirely in percentages.
- The transform in the onUpdate essentially makes the active pixel of the thumb the first pixel when scrolled to the top, the last pixel when scrolled to the bottom, and transitioning smoothly throughout the range. This is so that we don’t have to do any DOM measurements.
- Pressing escape when dragging resets the scroll position to the start and cancels the drag (like on Windows?)
- Step and Page moves can repeat if mouse button is held down. The repeating happens even if you move off the track, then back on. This is consistent with OS behavior (at least on macOS).
- Questions:
- What would it take to implement search highlights in the scrollbars
- are they dynamically resizable?
DockLayout
DockPanel
CommandPalette
- Provides a visual way for users to view and execute commands in the application command registry (in a similar way to menus)
- As with other leaf widgets, it takes an optional renderer. There is a default renderer, which is designed to be subclassed.
- The renderer has functions to render headers, items, and an empty message
- The default renderer is stateless.
- The command palette notably does not use a hierarchichal DOM node layout for headers/items. Instead it renders them as a flat list, which should be higher performance.
- A
layout
is not allowed to be added, since the palette is assumed to own all its content. - The API is intentionally extremely similar to that of menus.
The command palette exposes methods to remove items. This is distinct from the context menu and the command registry. Those objects are intended to be application singletons, so they deliberately have a more restrictive API, so that different plugins can’t necessarily interfere with each other.
- All of the rendering, fuzzy rendering, etc, happens in an update request handler.
- Keyboard modifiers are explicitly ignored by the command palette, so that it could add some handling for them in the future without breaking existing behavior
- When a command is executed, the palette goes back to the default state, clearing any state. This has proven to be contentious, and a lot of user feedback has focused on making it jump around less.
- An earlier version of the interface used colons in the search query to denote searching command palette headers. This was abandoned for a couple of reasons:
- Less discoverable
- More complex implementation
- Subtle issues around order-of-operation
The decision could be reversed, or made an extension point similar to the
IRenderer
- The fuzzy matching algorithm:
- Case insensitive
- Allows missing characters
- Penalizes missing characters
- If there’s no query, everybody matches
MenuBar
- As with menus, tabbars, you can provide a custom renderer, or subclassing the default renderer.
- The default
MenuBar.Renderer
implementation is designed to be subclassed. - Since the
MenuBar
handles its own DOM layout, it disallows setting a layout, so users can’t add children through public APIs. openActiveMenu()
is necessary to allow application authors to open the menu even if it is not the actively focused element.aboutToClose
andmenuRequested
signals are used to propagate events between menu bars and menus/submenus so that keyboard interactions work as expected.- In many cases, phosphor attaches event listeners with a life cycle (e.g., drag/drop), so they are not always active. The menu bar is an exception: it captures most mouse/keyboard interactions since we expect to to be interactive basically always.
ContextMenu
- The context menu is notably not a
Widget
. It is instead a class that manages creation and deletion ofMenu
widgets upon demand. addItem
returns anIDisposable
that can be used to remove context menu items. In this way, only the code that adds an item has access to the disposable. This is intended to allow extension authors to keep control over the items they add.- The
ContextMenu
uses a similar system to construct the menu as theCommandRegistry
uses for keyboard shortcuts. Based on the location of a click, it goes up the DOM hierarchy, testing against CSS selectors to see which commands might be enabled for a given click.
Menu
- The phosphor `Menu` widget is intended to be a leaf widget, in that it is not designed to contain other widgets
- The menu renders commands from a `CommandRegistry`, including mnemonics, keyboard shortcuts, labels, etc.
- A menu can take a `Menu.IRenderer`, which can be used to customize how menus are rendered into a node. If none is provided, then a default one is used that looks reasonable. The default `Menu.Renderer` is designed to be subclassed.
- Menus automatically handle keyboard navigation around the hierarchy, including arbitrarily nested submenus, as well as the menu bar. It accomplishes this by wiring up signals for `aboutToOpen` and `aboutToClose`, which can be used by parent and child menus. Most users of the `Menu` widget won’t need to use these signals.
- Keyboard navigation also handles skipping separators and disabled menu items.
- Internal open timers and close timers handle delays for opening and closing submenus. This makes interacting with submenus less jittery.
- You don’t insert menu items directly into the menu, rather you give `Menu.I``Item``Options`, and the menu uses those to create its menu item.
If a menu item is added while the menu is open, the menu closes before updating. This should mostly not happen except if an item is added due to some asynchronous operation (JupyterLab help links are one example).
- Overflow is hidden by default for all phosphor `Widget`s. This is really important for DOM rendering performance, as it keeps reflow boundaries isolated. However, this means that menus must be attached directly to the document body so that they show up on top of everything. Thus, they are absolutely positioned with client x,y locations.
- Menus are currently not aria-compatible — there are some subtle focus issues that need to be worked out.
- The default renderer automatically collapses duplicated separators.
TabPanel
- `TabPanel` is a widget that composes the `TabBar` and a `StackedPanel`
- Phosphor typically indicates a widget that has been created by another widget, and is semantically owned by that widget, with a CSS class. The stacked panel created by the tab panel is an example — it is tagged with `p-TabPanel-stackedPanel`. This allows for a somewhat more targeted class for keyboard shortcuts, context menus, etc.
- The `TabPanel` listens for a `widgetRemoved` signal on the `StackedPanel` to know when to remove a tab. This could also have been done with a message hook, but message hooks can block other message hooks, so it’s more reliable to wire up a signal connection.
- The `TabPanel`'s `TabBar` gets a dataset attribute `'``placement``'`, indicating which side of the stacked panel it is on. This allows you to target layouts with CSS.
- The `TabPanel` provides references to its underlying tab bar and stack panel, in case you need to do some operations on them that are not provided by the top-level API. However, mutating those objects may lead to undefined behavior.
- `insertWidget` automatically selects the new widget.
- Moving tabs in the tab bar will also trigger a reordering of widgets in the stacked panel. This will have no effect on the rendering, but does serve to keep the ordering of the tabs in sync with the DOM ordering of the widgets, which is useful to know when iterating over the widgets in JS.
FocusTracker
- `FocusTracker` is not itself a widget, but is a utility for tracking whether a widget is focused. It prevents the need to hook up focus events on individual widget DOM nodes.
- The distinction between `currentWidget` and `activeWidget` is a bit subtle. The docstring describes it, but briefly: the `currentWidget` is the most recently focused widget, and the `activeWidget` is the currently focused widget. So `currentWidget` may have a value even if `activeWidget` is null (indicating no widget in the set is focused).
- Items are automatically removed from the `FocusTracker` upon widget disposal, so no need to remove them when disposing of widgets.
- TabBar
- TabPanel
- (pushed to next week)
StackedLayout (inherits from PanelLayout; recall PanelLayout is for using CSS to do the layout)
- All children lay right on top of each other; their z-order is in the same order that they were added. Tab panels use this, just making the appropriate tab “on top” in the StackedLayout.
- Another use is to have overlays, where transparency is used to see many children composed together.
- attachWidget(): Called by the base panel layout class. Sends messages, and refits the parent.
- moveWidget(): This method is important since we’re using CSS for styling, we need to ensure the internal phosphor state of the child order matches the DOM tree child order.
- detatchWidget(): Undo of the `attachWidget()` and resets the z-index (incase you reuse, we don’t leave the z-index of the “old” state).
- Fitting must happen when children are hidden, etc.
- Fitting itself: Ignore hidden children, update the overall min/max as the max of the min/max of each child. If the parent has an ancestor, it needs to be refit as well (because our min/max has changed, thus it may push around its ancestors).
- Updating: Careful not to read from the DOM size attributes more than needed, because touching the DOM at all (even just reading attributes) can cause a reflow.
- StackedPanel (the sister class for StackedLayout)
- Takes optional options, like every other Panel class.
- Doesn’t add any extra API atop the base class
Panel
, except for one extra signal (widgetRemoved
).
- TabBar and TabPanel
- Pushing to next week because they will take a long time.
- SingletonLayout
- A layout that holds a single child widget.
- The intent is that you use CSS to layout your single child widget.
- Not an extension of PanelLayout, instead just extends Layout.
- Means it’s responsible for some extra things, like disposing of its single child.
- Setting a new child widget causes the old child to be disposed (unless you first set the child widget’s parent to NULL first).
- All layouts are iterable, thus so is this one. It just gives you one thing in an iterator.
- Handles the lifecyle as it’s supposed to, given that it doesn’t get to steal those from the PanelLayout.
- We don’t have the
SingletonPanel
yet! It would be easy to write, just needs to be done. - An alternative for
SingletonLayout
is to use aStackedLayout
with just one child, or get fancy and have two children, the top one being a spinner to show loading progress, for example.
- GridLayout (not to be confused with the DataGrid!)
- Extends
Layout
, thus has to do extra work to manage children on its own. - This is for laying things out in a grid, like “CSS Grid” but does some other things beyond css grid (?)
- Options to the c’tor, like normal.
- Row counts, column counts, row/col stretch factors, etc.
- The number of rows and columns in the grid is independent of the number of columns you put in. You can have a single widget span multiple rows/cols, for example. You can even overlap widgets. Basically the rows and columns come first, they get sized, and then you lay on top the widgets spanning 1 or more rows and 1 or more columns.
- When changing the stretch factor of a row/col, you need to update the children (they may move), but you don’t need to refit (the parent size didn’t change).
- Standard lifecycle management for the layout.
- Fitting: Ignore hidden children, tell teach child to do its own fitting, compute overall min/max sizing based on row/col spans, etc.
- Updating: Solve for children size using the BoxEngine, and then computing starting position of each row, and starting position of each col, then iterating through the items to set the children absolute position. Overall it’s an O(n) algorithm!
- Doesn’t yet have a
GridPanel
class. Great for someone new to the code to tackle!
- Extends
- BoxPanel class:
- This class, like many others, accepts a bag of options to the c’tor (i.e. a single object passed to the c’tor). This allows us to add more options in the future without breaking the API.
- The options are passed to the super c’tor (which is Panel). The Panel options are superclass elements of the BoxPanel option elements.
- Recall, CSS class names are prefixed with ‘p-’ and added to the DOM to each root element (wrt the Phosphor object)
- Simple class (BoxPanel). It just gives you an easy way to have a BoxLayout without having to instantiate it and install it on your own widget.
- SplitPanel class:
- Very similar in style and goal to the BoxPanel, but different enough to be its own class (e.g. SplitPanel has to deal with the mouse).
- Has an IRenderer to add virtual DOM nodes (e.g. the handle to drag the SplitPanel sections)
- Proxies most attributes to the underlying SplitLayout class, so let’s talk about that next! (below)
- SplitLayout class:
- Takes options similar to the BoxLayout
- The IRenderer is not optional!
- (However, at the SplitPanel layer, it will give you a default IRender if you don’t specify one.)
- Many getters/setters like BoxLayout
- Has more behavior to deal with normalizing pixels (convert to %s), then converting back to pixels.
- When you set sizes, “they don’t have to add to unity” since it will bet recomputed and normalized.
- Logic for dealing with resizing is not in the mouse event callbacks. This allows you to change sizes programmatically (a simulated drag, sorta).
- There’s a handled after every element (even the last one, but the last one is hidden and events on it are ignored). This allows the computation of sizes to be simpler.
- BoxEngine does the math for sizes of children when handle is dragged. Tries hard to only change the two widgets on either side of the handle.
- Takes options similar to the BoxLayout
- (back to) SplitPanel:
- Uses a default renderer if not specified in the options (and even uses the same object as the default renderer, which is okay to do since it has no state).
- The event listeners are defined here!
- A single method handles multiple events (not usually how you register listeners, but makes sense in this case).
- On mouse down, it’s important that we completely steal all mouse events for the whole document while it’s held down. We also globally override the cursor image while the mouse is down.
- growSizer function (from BoxEngine module):
- Can’t do this behavior in CSS, has to be driven by JS. Smartly grows and shrinks widgets in a logical way.
- Could maybe do something similar with CSS-grid, but Chris expects it will be more expensive with CSS regardless.
- Demo here: http://phosphorjs.github.io/examples/datagrid/
- If you have many widgets in a row, resizing one to the extreme will shrink its neighbors in a logical way.
- Can’t do this behavior in CSS, has to be driven by JS. Smartly grows and shrinks widgets in a logical way.
- BoxLayout
- Options to c’tor for directions, alignments
- Can get/set directions and alignments. Handles updating parent state upon being set.
- Completely override attachWidget, moveWidget, detatchWidget. Needs to do different stuff than the base implementation.
- This layout contains the first example of a widget that makes significant use of the widget lifecycle hooks. In particular, it avoids computation when the layout isn’t visible.
- Logic of fit() and logic of update() are different. Reason: Resize needs to be more efficient (it happens every mouse move when dragging, for example).
- PanelLayout
- Simplest concrete implementation of the Layout.
- Intent is to lay out your widgets with CSS (as opposed to JS-powered absolute positioning).
- Add children to parents which are laid out with CSS.
- Fulfills messaging protocol (as any layout).
- Commonly you will use the “Panel” widget (the PanelLayout counterpart).
- Layouts dispose of their children (the layout owns the children, thus will dispose them).
- The PanelLayout keeps an array of children (indexable).
- Adding a new child reparents it, and move it to the index you wish it to be at.
- If the PanelLayout has a parent, then we must actually do DOM manipulations when modifying children.
- Init method goes further than the base init method as it needs to attach the children to the actual DOM.
- All DOM manipulations are done in three protected methods: attachWidget(), moveWidget(), removeWidget().
- Some subclasses of PanelLayout will reimplement these methods.
- Overall goal is to keep DOM and Phosphor in-sync.
- moveWidget():
- In Chrome, moving is implemented as removing and inserting, so Phosphor just does this always (all browsers).
- Generally the sister widget and layout are named “ThingPanel” and “ThingLayout”.
- Exception is “Panel” (a widget) and “PanelLayout” (a layout).
- You can’t change the layout on a widget after its been first set.
- Panel:
- Uses a PanelLayout.
- If all you want to do is have a bunch of children laid out with CSS and throw it into a layout, use the Panel widget.
- The
boxengine
module.- Doesn’t depend on anything. It’s just a math module.
- Works in one-dimension using several
BoxSizer
s. - Three methods:
- calc: Docs are good description of what it does.
- Layout model
- fitPolicy: all layouts in phosphor obey this
- max sizes are not computed in many places. The fit policy used to have this, but it was taken away.
- If we wanna computer the minimum size of the layout in a different way than just summing the minimum size of the children, then we can use the fit layout to toggle this behavior.
- fitPolicy: all layouts in phosphor obey this
- Posting a message is useful, because it’s async and allows us to conflate messages. So multiple messages can be combined.
update
will only be called once per event loop. layout your children.- The phosphor message loop, which runs once per animation frame.
fit
:- Leaf nodes ignore this
- Those that have children remeasure their children
- goes up the tree
- When a children changes their min/max sizes, it sends a fit request to it’s parent.
resize
should be sent this whenever it may have changed it’s size. goes down the tree.- in CSS there is no concept of expanding a parent to fit it’s children. Here there is. It’s an inside out algorithm.
- this layout algorithm doesn’t have a name but is used by QT and other desktop layouts
activate
more generic than placing focus.close
default handler removes it from the hierarchy, it doesn’t dispose it or anything. You could intercept this with a message handler and throw up a dialogue box.- Clicking the
x
on the tab panel will trigger theclose-request
message to be sent.
- Clicking the
show
make it visible to the parent, might not be visible the page if parent is hiddenhide
inverse. Sends messages so that other things can react.
widgets
- Some circular dependencies between layouts and widgets
- This package is what most people associate with phosphor
- Most other packages were built to be used by widgets
widget
- Form a tree.
- Disposal: When you dispose of one node, you dispose it’s children.
- useful to remove widget from tab bar when it is disposed
- Resize message: Notifies a widget if it has been resized.
- It isn’t in the spec yet, but might at some point
- You need this to accomplish layouts that can’t be done with CSS. So to do this, you need to know if the parent has been resized, so you can refit the children.
- So to do this, either you need to either poll the dom or listen to the top level resize event.
- top level resize event works, but doesn’t account for resize events that aren’t about window dragging.
- The resize message propagates down in the widget hierarchy.
- Every phosphor widget represents one node in the DOM
addClass
: Can add class names, does this in a cross browser way- Convention is to call the classname same as JS class
node
: It is public, for convenience. It has too much functionality to wrap. Although there are many convenience methods on the node. If a widget has a layout, modifying the node externally could cause some issues.hidden
andvisible
flags- When you call hide, it sets display of dom node to None. Still in the document, but hidden for CSS purposes
- visible is not hidden, and none of ancestors are hidden.
- Not related to CSS visibility or hidden flags
- Can also get and set the parent of a widget.
set parent
shouldn’t be used, besides in layout.- Except if you wanna remove it from the hierarchy you set it to
null
.
- Except if you wanna remove it from the hierarchy you set it to
- Phosphor 2.0: Should we drop phosphor’s iterators and just the standard? They are less efficient but are standard.
- Common patterns
- Have getters that return
ISignal
which return privateSignal
so third parties can’t emit signals
- Have getters that return
- Attendees
- Saul Shanabrook / Quansight
- Ivan Ogasawara / Quansight
- Afshin Darian / Two Sigma
- Jacob Houssian / Quansight
- Katherine Oliphant / Quansight
dr``ag``drop
:- Complete implementation of drag and drop functionality within a single browser window. Doesn’t allow dragging from desktop to window or vice versa. Why? Because that would require using browser APIs. These are limited because you cannot programmatically start and stop drag operations.
- Why is that an issue? If you wanna have some behavior where drag behavior doesn’t start until user is dragging 15 px away from UI, you can’t do this in native drag operations. Only way to do it natively is with “draggable” attribute on DOM node, so you can’t do this.
- As soon as you start handling mouse events yourself, you disable the drag and drop standards. So this required this package, which allows you to switch back and forth between native APIs and these custom ones.
- Also built in supports only one type of data to store on drag event, strings. We can drag and drop custom JS objects, because it’s all in one page.
- Followed the spec on the processing model
- The
IDragEvent
is different but the process should be the same
- The
supportedActions
are set by creator of drag eventdropAction
are set by consumer of drag eventmimeData
is basically an ordered key value store of mimetypes to data.source
is provided by creator. Used to pass around some extra metadata.Drag
You are going to create this, when the user does something. For example, it is used in the tab bar, to see once user extends beyond some threshold for dragging.- most options are copied onto the event
start
: clientX and clientY should be current x and y of users cursor. Only way to get this information is from event, so you have to grab this from the event.- between the time you call start and the time the promise resolves, phosphor takes control of mouse and keyboard. So you can assume all that will happen is user’s input will be used for dragging. You won’t get access to any of the keypress or mouse events.
Drag.overrideCursor
: Allows you to overwrite the cursor the browser no matter where user is hovering.- Question: Can user press modifier while dragging to change intent?
- Not right now. It isn’t implemented in native standard, but it could be added.
- We would have to modify the event handling logic to check modifiers held on mouse move.
- If this is needed we should open a feature request.
- Question: Can we combine this with browser drag and drop?
- If you wanna drag file from filebrowser to desktop, this isn’t possible.
- Complete implementation of drag and drop functionality within a single browser window. Doesn’t allow dragging from desktop to window or vice versa. Why? Because that would require using browser APIs. These are limited because you cannot programmatically start and stop drag operations.
vdom
- React:
- It had a weird licence before.
- All we wanted was virutal dom and difffing, not the rest of it.
- Back in the day, there was not typescript support for react.
- I imagine for Phosphor 2.0 we would deprecate this and depend on React.
- New users should probably use react for leaf nodes
- React:
widgets/title
- Generic class with some object
T
.
- Generic class with some object
- Attendees
- Saul Shanabrook / Quansight
- Ivan Ogasawara / Quansight
- Afshin Darian / Two Sigma
- Katherine Oliphant / Quansight
domutils
- Similar to
coreutils
except these utilities have a dependency on the DOM. elements
: Related to DOM elementsboxSizing
allows you get get padding/sizing of an element. This is important when doing explicit resizing. Instead of getting some sizes and then setting some, we get them all in one chunk. Mixing read/writes for styles isn’t performant.sizeLimits
: Converts between sizinghitTest
: See if element is over a certain position.scrollIntoViewIfNeeded
: Really helpful for manual scrolling. Used in the notebook to focus a particular cell. If that cell is not entirely visible, this function can be called with the canvas element that contains the relevant element and then the target element. It will scroll if needed.
platform
:- simple platform detection, used for keybindings or bugs based on browser.
- Most in phosphor is browser agnostic but some spots rely on it.
selector
:- allows you to understand the specificity of a CSS selector
- Used by the keyboard shortcut to choose the most specific selector and execute that keybinding.
- This is specified by the CSS standard.
isValid
: Allows you to compute whether a specifier is valid. Useful for proper error handlermatches
: All browsers implement this, but some browsers dont implement it correctly, so this generalizes across browser. It also has a slow cache by querying document for all elements and check if select matches that element.
- Similar to
keyboard
- Browsers can’t agree on key events so this module has to exist.
- “Handling key events robustly in the browser is probably the most difficult thing to do”
- There are three ways to determine what key the user presses. All but one on are deprecated.
- We are currently used the
keycode
property, even though it is deprecated, because most browsers behave the same and implement it. - The key provided by the browser is the key code, like 105. However, this isn’t the key, because it depends on their keyboard layout. So
105
corresponds to different letters depending on locality. - So we need way to translate between these key codes to the characters the user pressed.
IKeyboardLayout
: So we create an abstraction for different keyboard layouts. The meat here iskeyForKeydownEvent
which returns the string of the character the user pressed from the keyboard event.- Has a singleton keyboard layout for the application. Because it’s very unlikely that the user will have two keyboards plugged in. So there is just one globally.
- On startup, JupyterLab could pick which layout is appropriate for the user and call
setKeyboardLayout
.
KeycodeLayout
is an implementation- This uses the keycode, and takes in a mapping of key codes to character strings.
- Could prompt user to press each key on keyboard and type in character.
- We have uppercase characters because most keyboards print the uppercase versions. Also if we displayed lowercase, then people might be confused if SHIFT changed the character.
commands
:- single
CommandRegistry
class - This is an implementation of the "command” pattern
- Where you have some action you wanna take where you don’t care about result but you wanna execute it.
- We implement this as a registry. Why a registry, with string names, instead of independent objects? So that you can setup keybindings as JSON on disk, so that you can refer to this command in JSON.
- There are two uses here: Author of command who registers this. Author of keybinding who registers keybinding for this command.
- There are signals for when commands have changed, when a command is executed (for debugging), when a keybinding is changed.
- command IDs should be unique. So you should namespace these with your module.
- All the options are for building up UI around the command. Command registry does nothing with these options (except for
isEnabled
).- Usage: has example of the command in longer document
- isEnabled: visual representation of whether we can execute it.
- There is no remove command, because you should be responsible for removing it yourself. By using the dispose returns from
addCommand
. notifyCommandChanged
: Call this when you change the metadata attached to a command.- If I would go back, we would remove the ID as an arg, and we would just re-render totally whenever any changes a command. Because you often do a change that effects the metadata of a number of commands.
args
: Has to be readonly JSON because they could be in a file, so they have to be JSON.execute
: Calls command and returns promise for result.- Questions:
- Why allow a return value?
- Often we return a promise to something that is available after the command is executed.
- In jupyterlab we have many args that are just used for context generation. Basically is there a way to separate out args that are for metadata vs args for the execution of the commands
- Maybe it would be helpful to separate the metadata generation from commandargs.
- We could do this in command palette by passing different args to the different methods. The registry doesn’t care what args you pass it, it just passes those on.
- Why allow a return value?
- single
- General patterns
- Every package has a
index.ts
that simply exports all public method from other files. So you always import from the package itself (coreutils
), not a sub file (coreutils/elements
)
- Every package has a
- Attendees
- Saul Shanabrook / Quansight
- Afshin Darian / Two Sigma
- Ivan Ogasawara / Quansight
- Jacob Houssian / Quansight
- Ryan Henning / Quansight
- Katherine Oliphant / Quansight
properties
- Copied from “C# attached property”
- more verbose than in C# since it isn’t first class in JS
- Type safe way to store data “on an object” you don’t own
- i.e. you are dealing with a library and they hand you back a widget. You want to store extra data associated with that widget in some way. You can’t assign properties to it because it isn’t type safe. You can’t subclass because you are given it.
properties
allow you keep this property around with a lifetime attached to the object by using aWeakMap
, so that the properties are GC after the object is.- Don’t use this for your own objects. For that, it’s much more efficient to just put it in the class.
- Usage:
- Used in
widgets
package to store thetitle
. It is created on demand, since not all widgets don’t need titles, so that is why it is a property not in the class itself. This way you don’t pay overhead of having this title object when it isn’t needed. - Various properties BoxLayout needs from widgets are set as properties. But these are hidden from the user by having getters/setters.
- allows layout to be notified when properties are changed.
- Used in
- To access property you have to pass in owner/instance.
- Options:
name
: Just for show, has no effectcreate
: creates property when it is accessed the first time. Isn’t stored until it is first accessed.changed
: allows you to provide a callback when the attribute changes.coerce
: Allows us to transform the property when it is assignedcompare
: Allows you to define comparison to check if property is different and should be updated.
- Methods:
get
/set
clearData
removes the data associated with the property.- Called in base widget class in dispose method
- Copied from “C# attached property”
coreutils
- Coreutils don’t have DOM dependency, whereas domutils do have a dependency.
json
: type definitions for JSON. Also includes type guards and deep copy/equals.mime
: Mime data type: ordered key value mapPromiseDelegate
: Allows you to create a promise and reject/resolve it later.random
: Getting crypto secure random valuesuuid
: create fast uuid4 values. If you don’t need globally unique ID, you can just increment IDstoken
: Interfaces are erased at compile time. Sometimes we want to tell which interface something implements or tell between them. We can use aToken
to maintain typing information, associated with a certain value.- Has a
name
associated with it and a structural property just to preserve the type, we don’t use it at runtime. - Used in JupyterLab for two things with the same name, but one is type and one is value. For when we are defining plugins.
- Instead of using
token
we could use decorators and decorator metadata to assemble dependency injection framework. But since decorators are not finalized, they might change, we didn’t go this route. We also didn’t want someone to have to write typescript, which wouldn’t be possible with decorators since they aren’t part of the spec. - You could also just use unique strings, but then you could mispell it easily and wouldn’t get a compile time check.
- Has a
- Notes:
- Private namespaces are defined at the bottom of the object
- Questions:
- Attendees
- Ryan Henning / Quansight
- Saul Shanabrook / Quansight
- Ivan Ogasawara / Quansight
- Igor Derke / Quansight
- Tony Fast / Quansight
- Jason Grout / Bloomberg
- Afshin Darian / Two Sigma
- Jacob Houssian / Quansight
- We have recording this week thanks to Luciano on WebEx.
signals
:- “Contrapositive” to messaging.
- One to many communication, vs many to one (messaging)
- Signals are about
- Signals are like JS events but not tied to dom hierarchy, so no “bubbling” up.
- Any number can subscribe to a signal with a callback called a
slot
. (lifted from QT) ISignal
is immutable interface, allowing only connecting or disconnecting. So that classes can expose this, attachSignal
as private attribute, so that external code can’t emit, only listen.- The first argument of the slot is the sender, the second is the value.
- When you
emit
from a signal, this is analogous to sending aMessage
, i.e. all slots are invoked synchronously. But you don’t expect theemit
method to throw, so any exceptions that are thrown will be caught and logged to the console. - There are some static methods on
Signal
to disconnect all receivers and senders of a certain object. We use theclearData
in the dispose method of the widget to remove all its connections. - All connections are stored with weakmaps, so if your object falls out of scope (is garbage collected) it will be cleared.
- Similar to
event-emitter
package in Node. - What about composing signals?
- Observables are more about push based data, where as signals are more like events. Composing signals (mapping/filtering, etc) in a functional way isn’t how we think of signals. Instead we could create a new signal and subscribe to an old one and emit on the new one. We shouldn’t use the functional patterns because they don’t map well with the garbage collection with regards to senders.
- “Contrapositive” to messaging.
Disposable
- Track resources and handle cleanups throughout application.
- in C# this is called the
Disposable
pattern. Object hasdispose
method. Guarantee is that every call after the first is a no-op. Also has a way to retrieve whether it has been disposed. - Has disposable data structures as well, that dispose their contents on disposal.
- What if there was a convenience class that has logic to add disposed signal?
- Chris isn’t opposed to adding additional interface to add signal to interface.
- He is opposed to making a convenience class, because then you feel encouraged to inherit this base class, and requires having all this logic embedded in your class. Don’t want to encourage this type of behavior.
- DisposableSet don’t have a disposed signal because of memory overhead.
- Attendees
- Saul Shanabrook / Quansight
- Ryan Henning / Quansight
- Chris Colbert
- Katherine Oliphant / Quansight
- Igor Derke / Quansight
- Afshin Darian / Two Sigma
- Jacob Houssian / Quansight
- Jason Grout / Bloomberg
- Vidar Fauske / Simula Research Laboratory
- Ivan Ogasawara / Quansight
- Luciano / IBM
- Rich Context is some new work for JupyterLab
- Datasets and data converters
- Commenting and annotation: click on file/cell and add comments
- Metadata: Have some metadata associated with a dataset
- Possible agenda:
- Walk through particular packages in Phosphor and walk through them. Starting from bottom of the stack.
- Hear about alternative ways these could be implemented, to understand where they came from.
- Those already working on jupyterlab know least about datastore and datagrid packages, the most recent work.
- Datastore is work we need to hand off and contribute to most actively.
- How to add comments to Data Grid?
- Walk through particular packages in Phosphor and walk through them. Starting from bottom of the stack.
- Different needs:
- Getting familiar with Phosphor in general
- Getting familiar with Datastore in particular
- Next time: maybe record meeting? Lots of information.
- Useful artifact could be design document describing phosphor at a high level.
- Phospher isn’t one thing, it’s a collection of several packages (
./packages
)- Each one can stand by itself, although there is some interconnections
- No external dependencies (0)
algorithm
andcollections
are at the bottom.algorithm
: JS doesn’t come with many core data types. (similar to C++ std algorithm)iterators
came before ES6 iterators, and are strictly more powerful.- You can clone phosphor iterators which you can’t do with ES6 iterators.
- Calling next returns value or undefined, not
valid
done
pair, which is faster. - There is some debate on whether to deprecate these and move to ES6 iterator. This could be a 2.0 change, because it is breaking.
- They all work on iterators, where as standard JS algorithms all work on arrays, nott iterators (
map
, etc). - Also supports more algorithms than JS like
topologicalSort
. Yes you could depend on NPM package for this, but then you end in dependency hell - Like lodash or jquery
collections
- small package, but could grow in future with more data structures
- implement phosphor iterator protocol
- Linked list
- used for queue
- better algorithmic complexity for pushing/popping than JS array
- b plus tree
- ordered key value map
- useful for ordered collection of things with logarithmic time for all operations
- used in the datastore
messaging
- Implementation of QT’s event pattern, without event bubbling
- didn’t call it events to differentiate from JS events.
- In JS, multiple consumers can subscribe to one event emitter. One to many.
- Phosphor messaging pattern is many to one. Many message creators and one listener
IMessageHandler
: interface that allows you to opt in to handling messages of certain types, but it doesn’t have to (processMessage should not throw an error on unrecognized messages)- Many objects handed to me, but I need one to be implement some behavior.
- I could have a method on them that can implement some named method.
- i.e. a protocol
- Instead can message passing algorithm. You have to implement a
processMessage
method and you get a string of the message type.- then you only have to implement one method instead of a lot of them
- processMessage essentially is a level of indirection for implementing functionality, with its own namespace (so you don’t have to mess up the method namespace)
- Used heavily inside of widgets, for attachment notifications, resize messages, paint notifications, layout messages, close message
- I could have a method on them that can implement some named method.
- Can “post” a message ( “deferred”/ asynchronous processing) or “send” a message (synchronous processing). “post” vs “send” naming familiar from win32 api?
- Deferred message can be “conflated”. Subclass of message
ConflatableMessage
that we can use to combine a bunch of messages as one, when we dispatch them. - If we have multiple conflatable messages of the same type in the event queue then we combine (conflate) them and replace the one in the front of the queue.
- i.e
PaintRequest
implements conflate method, by expanding “dirty” bands to contain scope of both of them. - Earliest message in queue gets notified about later messages in queue and has an option to conflate it (i.e., update itself from the contents of the later message, and return true to remove the later message from the queue).
- conflatable messages should be able to be reordered with respect to other types of messages and have no effect. because they can be conflated and merged.
- Deferred message can be “conflated”. Subclass of message
- Many objects handed to me, but I need one to be implement some behavior.
IMessageHook
allows us to spy on messages that are sent or filter them- if you are a parent widget and want to know if child is received, you can install a message hook on the child’s resize event, to intercept it and return
false
to filter it out. - This happens in a
close-request
method on widgets, any widget can be prevented from being closed. i.e. could have a popup to stop it from being closed. - Also used in ipywidgets to allow resize events to be handled by it’s own handler.
- if you are a parent widget and want to know if child is received, you can install a message hook on the child’s resize event, to intercept it and return
MessageLoop
is a namespace that takes care of all this. Global, only one at a time.sendMessage
runs all hooks and sends (?) message if all hooks return trueinstallMessageHook
/removeMessageHook
add remove message hooks for handlerclearData
removes all message hooks and all messages in the queue for a certain handler.- called in destroy method on a widget to clear all data from message queue.
- Also functions for getting/setting exception handler.
- Whenever you send or post a message, the handler might throw an exception. From an API perspective you don’t wanna worry about this. So message loop guards execution in
try…catch
and logs error to console by default. - can set a new behavior.
- mostly for debugging, handlers shouldn’t throw exceptions.
- Whenever you send or post a message, the handler might throw an exception. From an API perspective you don’t wanna worry about this. So message loop guards execution in
- Phospher isn’t one thing, it’s a collection of several packages (
- Conclusions
- Before next meeting figure out how to record
- Luciano volunteered IBMs chat software
- Jason volunteered the jupyter zoom channel
- Jason suggested that we take these notes and compile them to design documents and have chris review them to create documentation
- Before next meeting figure out how to record