In the previous two posts in this series, I reviewed the HTML DOM structure or rendered notebook markdown and code cells in JupyterLab (I assume the same structure exists in RetroLab, but need to check…). In this post, I’ll review one way in which we can set classes on those elements from a JupyterLab extension.
To simplify the extension building process, the following recipe has been tested using the JupyterLab Plugin Playground, a JupyterLab extension that lets you write and test simple extensions purely within a JupyterLab context (i.e. without any need to build and install an extension from the command line).
To begin with, I’ll see if I can create a simple extension to add class tags to various parts of the cell HTML DOM based on the presence of cell metadata tags.
Docs for how to drive changes from notebook events seem to be in JupyterLab API notebook module notebookactions, but they aren’t relevant if you want to take action over a notebook on load, as, for example, when cells have been rendered into the DOM and are available for classing.
The notebook class docs briefly reference various accessor methods that may be connected to. I thought a promising one (from the name of it) might be the
fullyrendered accessor, which seems to be “[a] signal emitted when the notebook is fully rendered” but a Stack Overflow comment suggests otherwise:
notebook.fullyRenderedis fairly new and may not be what you are looking for. Previously there was no concept of “notebook.ready” (it was just cells getting added one by one on load), but with the virtualized rendering – if configured to rely solely on content intersection) users may never see a fully rendered notebook (if they do not scroll down to the very last cell); in any case this is all subject to change based on the feedback we got about problems with this feature (a lot of changes were already made), so personally I would just go one-by-one on every change.
That said, I found the
fullyRendered signal did fire, albeit multiple times, when I loaded a notebook and could be used to class the cells based on metadata, but the
modelChanged signal did not seem to do anyhting when the notebook loaded, when I added metadata to a cell, or when I moved a cell? (I note there is also a
Just as an aside, I note some signals that may be useful for a cell execution status extension in the notebookActions class, specifically the
executed (“A signal that emits whenever a cell execution is scheduled.”) and
executionScheduled (“A signal that emits whenever a cell execution is scheduled”) signals, although I note that
selectionExecuted also claims to be “A signal that emits whenever a cell execution is scheduled”? An example of connecting to the
NotebookActions.executed signal can be found in the
jupyterlab-cell-flash extension. I’m not sure if there’s a way of detecting whether the execution ran successfully or returned an error? There
NotebookActions.executed signal does seem to return an
error? (yes, the
? is there…)
KernelError object and a
success boolean, so maybe we can use those. In the classic cell execution status extension, it looks like there was a code cell
_handle_execute_reply action that returned various status messages (
aborted) that could be reasoned around.
Another possibly useful crib is the
agoose77/jupyterlab-imarkdown extension which resembles the classic notebook Python Markdown extension which renders variables into markdown content (via this discourse forum post by @agoose77).
Anywhere, here’s the code I used, which was largely inspired by @krassowski’s response to my Stack Overflow question on Accessing notebook cell metadata and HTML class attributes in JupyterLab Extensions.
With the extension, when I open a notebook with some
class-TEST tags on various markdown and code cells, the following class attributes are set on a markdown cell:
And the following class attributes are set on a code cell:
Having set classes on various elements, we now need to style them. An example of how to do that will (hopefully!) be provided in the next post in this series…
PS Just as an aside, one interesting possibility that occurs to me is if we can intercept a cell run command and swap in our own execution call, we could define “magic-tags” that, if detected on a cell, allow us to grab the cell content and then treat it in a magic way. That is, rather than use a block magic at the start of a cell, magic-tag the cell and treat its contents via some tag powered magic. Various bits of signalling around cell execution scheduling requests might be relevant here, eg in the context of trying to set up a pipeline around the processing of code in a code cell before (and after) its execution.