1

I would like to listen to cell events of JupyterLab notebooks (version 3.2.0) in a custom extension. How can I do so?

a) Searching the documentation on "events" did not give helpful information:

https://jupyterlab.readthedocs.io/en/latest/search.html?q=events&check_keywords=yes&area=default

b) Here is some outdated example I could find:

Jupyter.events.on('execute.CodeCell', function(evt, data) {
    // data.cell is the cell object
});

https://github.com/jupyter/notebook/issues/2321

c) Here is another outdated code snipped:

require([
    "base/js/namespace",
    "base/js/events"
], 
    function(Jupyter, events){
        console.log("hello2");  
        events.on('notebook_loaded.Notebook', function(){
            console.log("hello3");
        });
        events.on('app_initialized.NotebookApp', function(){
            console.log("hello4");
        });
});

https://github.com/jupyter/notebook/issues/2499

d) I would expect to use something like

app.events

or

var { Events } = require('@jupyterlab/events');

However, those variants did not work.

Edit

I found another code snippet:

panel.notebook.model.cells.changed.connect((list, changed) => {
            if (changed.type == 'add') {
                each(changed.newValues, cellmodel => {
                    let cell = panel.notebook.widgets.find(x => x.model.id === cellmodel.id);
                    // do something with cell widget.
                });
            }
        });

https://github.com/jupyterlab/jupyterlab/issues/4316

Maybe there is no global "event registry" any more, but the events need to be accessed via the model property?

Related:

Where is a docs for Jupyter front-end extensions JavaScript API?

Stefan
  • 10,010
  • 7
  • 61
  • 117

1 Answers1

4

JupyterLab Extensions should listen to application-related events using the signal pattern which is based on lumino Signal implementation.

You can identify the signal of interest in the reference JupyterLab API Reference (which is also linked from the documentation as the last entry), for example, cell execution signal is available as NotebookActions.executed.

Extension Example

https://github.com/jupyterlab/extension-examples/tree/master/signals

Signals of NotebookActions

Signal of observable list

Signals of individual cells

The cell model provides the signals

Usage example:

function __observeNotebook(app, dependencies){  

    let notebook = __tryToGetNotebook(app);
    if(notebook){       
        let cellModels = notebook.model.cells
        cellModels.changed.connect(__cellsChanged, this);   

        for(let cellIndex=0; cellIndex < cellModels.length; cellIndex++){
            let cellModel = cellModels.get(cellIndex);          
            __observeCell(cellModel, notebook);
        }

        let notebookActions = dependencies["NotebookActions"];
        notebookActions.executed.connect(__cellExecuted, this);  //selectionExecuted, exutionScheduled
    }        

}

function __observeCell(cellModel, notebook){   
    cellModel.contentChanged.connect(cellModel => __cellContentChanged(cellModel, notebook), this);   
    cellModel.stateChanged.connect(__cellStateChanged, this);   
}

function __cellsChanged(cellModels, change){
    console.log("Cells changed:")
    console.log("type: " + change.type);
    console.log("oldIndex: " + change.oldIndex);
    console.log("newIndex: " + change.newIndex);
    console.log("oldValues: " + change.oldValues); 
    console.log("newValues: " + change.newValues); 

    if(change.type == "add"){
        var newCellModel = cellModels.get(change.newIndex);
        __observeCell(newCellModel);
    }
}

function __cellContentChanged(cellModel, notebook){ 
    let id = cellModel.id
    console.log("Content of cell " + id + " changed");

    let currentText =  cellModel.value.text;
    console.log(currentText);
   
    let cellWidget = notebook.widgets.find(widget=>{
        return widget.model.id == id;
    });

    let outputArea = cellWidget.outputArea;
    let children = outputArea.node.children;
    if(children.length==1){
        let output = children[0];
        console.log(output);
    }
   
}

function __cellStateChanged(cellModel, change){
    let currentText =  cellModel.value.text;
    console.log("State of cell " + cellModel.id + " changed:");
    console.log("name: " + change.name);
    console.log("old value: " + change.oldValue);
    console.log("new value: " + change.newValue);
}

function __cellExecuted(any, context){
    let {cell, notebook, success, error} = context; 
    console.log("Executed cell " + cell.model.id);
}


function __tryToGetNotebookCell(app){   
    var notebook = __tryToGetNotebook(app);
    return notebook
        ?notebook.activeCell
        :null;      
}

function __tryToGetNotebook(app){
    var notebookPanel = __getFirstVisibleNotebookPanel(app);
    return notebookPanel
        ?notebookPanel.content
        :null;
}


function __getFirstVisibleNotebookPanel(app){
    var mainWidgets = app.shell.widgets('main');
    var widget = mainWidgets.next();
    while(widget){
        var type = widget.sessionContext.type;
        if(type == 'notebook'){  //other wigets might be of type DocumentWidget
            if (widget.isVisible){
                return widget;
            }
        }
        widget = mainWidgets.next();
    }
    return null;
}
Stefan
  • 10,010
  • 7
  • 61
  • 117
  • 2
    I added some info for my draft answer as you clearly figured it out already. Please modify to your style if you prefer. – krassowski Oct 21 '21 at 13:13
  • 1
    Here is a follow-up question on how to access cell output: https://discourse.jupyter.org/t/how-to-get-output-model-for-a-given-cell-in-a-jupyterlab-extension/11342/2 – Stefan Oct 22 '21 at 08:01
  • 1
    I encounter the exact same problem, thank you so much for your solution! The [signal tutorial](https://github.com/jupyterlab/extension-examples/tree/master/signals) is extremely helpful. – Jay Wang Nov 12 '21 at 17:49