JupyterFrontEndPlugin event listener for window and document events?

I’m changing a single icon of a specific named folder in either the directory list or the breadcrumb when the directory listing is loaded. I’m able to do this in my frontend extension’s activate(), but I have to add a delay because the DOM is not yet created on extension activation. Ideally there is a hook for when JL’s directory listing changes because I need to re-call the code at that time.
How can I listen to document or window events within the JupyterFrontEndPlugin activate function? I find it strange that I am unable to use document.addEventListener() but I am able to use document.getElementsByClassName(). I have the following so far. JupyterLab 4.3.0.

index.ts

import {
  JupyterFrontEnd,
  JupyterFrontEndPlugin
} from '@jupyterlab/application';

const extension: JupyterFrontEndPlugin<void> = {
  id: 'my_first_extension:plugin',
  description: 'my first plugin',
  autoStart: true,
  activate: (
    app: JupyterFrontEnd,
  ) => {

    // add event listeners - three of four calls yield no output in the console
    window.addEventListener("load", (event) => {
      console.log("loaded", event);
    });
    window.addEventListener("unload", (event) => {
      console.log("unloaded", event);
      // this works when we leave the page
    });
    document.addEventListener("readystatechange", (event) => {
      console.log(`readystate: ${document.readyState}`, event);
    });
    document.addEventListener("DOMContentLoaded", (event) => {
      console.log(`DOMContentLoaded`, event);
    });

    // this code works correctly after 3 seconds have elapsed
    setTimeout(() => {
      // replace the folder icon in the directory list
        const folderIconElements = document.getElementsByClassName('jp-DirListing-itemName') as HTMLCollectionOf<HTMLElement>;
        for (let i = 0; i < folderIconElements.length; i++) {
          if (folderIconElements[i]['innerText'] === 'my directory') {
            const children = folderIconElements[i].children;
            for (let j = 0; j < children.length; j++) {
              if (j === 0) {
                // replace folder svg with folderFavorite
                children[j].innerHTML = children[j].innerHTML.replace(
                  'd="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8z"',
                  'd="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2m-2.06 11L15 15.28 12.06 17l.78-3.33-2.59-2.24 3.41-.29L15 8l1.34 3.14 3.41.29-2.59 2.24z"'
                );
              }
            }
          }
        }

        // replace the folder icon in the breadcrumb
        const breadcrumbElement = document.getElementsByClassName('jp-FileBrowser-crumbs') as HTMLCollectionOf<HTMLElement>;
        const bChildren = breadcrumbElement[0].children;
        if (bChildren.length > 3 && bChildren[2].innerHTML === 'my directory') {
          bChildren[0].innerHTML = bChildren[0].innerHTML.replace(
            'd="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8z"',
            'd="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2m-2.06 11L15 15.28 12.06 17l.78-3.33-2.59-2.24 3.41-.29L15 8l1.34 3.14 3.41.29-2.59 2.24z"'
          );
        }
    }, 3000);

    console.log('my first extension is activated!');
  }
};
export default extension;

I did not understand how to use use app.handleEvent() (only seems to register keyup, keydown, resize, and context menu), or addEventListeners (v3.x?) as an element in the JupyterFrontEndPlugin definition (after autoStart and before the activate: () => {} definition).

I had some success with a MutationObserver on document.getElementsByClassName("jp-DirListing-content")[0], and am working through that, but would prefer if there was a built in event/hook for when the directory changes.

1 Like

I’m changing a single icon of a specific named folder in either the directory list

I think this is not the best way to approach the problem. While you could listen to filebrowser signals to invoke your logic after each render, I would suggest that instead of trying to change the DOM, you could look into just telling JupyterLab to render specific icons for your pre-defined name (say “my directory”). This was discussed before and is supported in JupyterLab 4.2+:

see my comment in that thread. In principle maybe there should be a similar way to specify breadcrumbs icon but I am not sure what exactly are you aiming to achieve with that one.

This solution for changing the folder based on name worked for me.

    const myFolderName = 'hello_folder'
    app.docRegistry.addFileType({
      name: 'my_special_folder',
      contentType: 'directory',
      icon: folderFavoriteIcon,
      pattern: '^' + myFolderName + '$'
    });

I would expect JL to show the above icon in the breadcrumbs when I click into the directory, but it does not (defect?), so for now I am relying on a DOM change. But will attempt to explore more when I have an opportunity and post my findings here. Thanks!

1 Like

I would expect JL to show the above icon in the breadcrumbs when I click into the directory, but it does not (defect?),

If you would like this to happen please open an enhancement issue on Issues · jupyterlab/jupyterlab · GitHub and I can provide pointers on what changes in code would be needed.

1 Like

Done, thanks!

1 Like