'Accessing notebook cell metadata and HTML class attributes in JupyterLab Extensions
In a minimum viable JupyterLab extension, as for example tested using the JupyterLab Plugin Playground, how can I add a toolbar button that will toggle a particular class attribute on the HTML associated with one or more selected notebook cells (either code cell or markdown cell)?
To generalise the example further:
- how would I apply different class attributes to code cells and markdown cells?
- how would I add a class to the HTML based on the the presence of a particular metadata attribute or metadata tag element in the notebook JSON structure?
As a starting point the following code (taken from the JupyterLab extension examples) should add a button to the toolbar:
import { IDisposable, DisposableDelegate } from '@lumino/disposable';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '@jupyterlab/application';
import { ToolbarButton } from '@jupyterlab/apputils';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import {
NotebookActions,
NotebookPanel,
INotebookModel,
} from '@jupyterlab/notebook';
/**
* The plugin registration information.
*/
const plugin: JupyterFrontEndPlugin<void> = {
activate,
id: 'toolbar-button',
autoStart: true,
};
/**
* A notebook widget extension that adds a button to the toolbar.
*/
export class ButtonExtension
implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel>
{
/**
* Create a new extension for the notebook panel widget.
*
* @param panel Notebook panel
* @param context Notebook context
* @returns Disposable on the added button
*/
createNew(
panel: NotebookPanel,
context: DocumentRegistry.IContext<INotebookModel>
): IDisposable {
const myButtonAction = () => {
// Perform some action
};
const button = new ToolbarButton({
className: 'my-action-button',
label: 'My Button',
onClick: myButtonAction,
tooltip: 'Perform My Button action',
});
panel.toolbar.insertItem(10, 'myNewAction', button);
return new DisposableDelegate(() => {
button.dispose();
});
}
}
/**
* Activate the extension.
*
* @param app Main application object
*/
function activate(app: JupyterFrontEnd): void {
app.docRegistry.addWidgetExtension('Notebook', new ButtonExtension());
}
/**
* Export the plugin as default.
*/
export default plugin;
Solution 1:[1]
You shall start by getting a handle of the Notebook class instance (which is the content of the notebook panel which you already have available):
// in anonymous function assigned to myButtonAction
const notebook = panel.content;
The notebook instance provides you with the active Cell widget:
const activeCell = notebook.activeCell;
The cell widget has two attributes of interest: model which enables you to access the metadata, and node which enables manipulation of the DOM structure.
For example, you could toggle class of the node of a cell if it is a markdown cell (=ICellModel has .type (CellType) equal to 'markdown'):
if (activeCell.model.type === 'markdown') {
activeCell.node.classList.toggle('someClass');
}
The metadata is stored in cell.model.metadata.
For selection of cells something as follows should work:
const {head, anchor} = notebook.getContiguousSelection();
if (head === null || anchor === null) {
// no selection
return;
}
const start = head > anchor ? anchor : head;
const end = head > anchor ? head : anchor;
for (let cellIndex = start; cellIndex <= end; cellIndex++) {
const cell = notebook.widgets[cellIndex];
if (cell.model.type === 'code') {
cell.node.classList.toggle('someOtherClass');
}
}
There is a problem with this approach however, as when the notebook gets opened in a separate view, or simply reloaded, the classes will go away (as they are only toggled on the DOM nodes). If you require persistence, I would recommend to:
- use the button to only write to cell metadata
- add a separate callback function which will listen to any changes to the notebook model, roughly (not tested!):
which should also work (in principle) with collaborative editing.// in createNew() const notebook = panel.content; notebook.modelChanged.connect((notebook) => { // iterate cells and toggle DOM classes as needed, e.g. for (const cell of notebook.widgets) { if (cell.model.metadata.get('someMetaData')) { cell.node.classList.toggle('someOtherClass'); } } });
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | krassowski |
