Action Button in UI and Cell Evaluation
A ready-to-go example is available in this repository. Clone it to <AppData>/wljs-notebook/wljs_packages
using:
git clone https://github.com/JerryI/wljs-plugin-example-2
Then restart WLJS Notebook.
In this short tutorial, we will discuss how to add a basic UI element to the WLJS Notebook interface and cover some fundamentals of cell evaluation. In general, we need to create a package for the main kernel.
Summary
This tutorial will cover:
- Creating a package for the main kernel that adds a new UI action button and a handler function to evaluate all cells in a notebook.
- Writing a UI action button template using WLX.
- Adding a native top-menu button that duplicates the action of the UI button.
Preparations
Use the wljs-plugin-template to create a new repository, then clone it to the <AppData>/wljs-notebook/wljs_packages
folder. For example:
git clone https://github.com/JerryI/wljs-plugin-example-2
Next, edit the contents of package.json
:
{
"name": "wljs-plugin-example-2",
"version": "0.0.1",
"description": "An example plugin for WLJS Notebook",
"wljs-meta": {
"frontend": ["src/Frontend.wl"],
"priority": 5000,
"category": "Notebook Extensions",
"menu": [
{
"label": "Example Button Evaluate",
"event": "example-eval-all",
"spawnWindow": false,
"type": "button"
}
]
},
"repository": {
"type": "git",
"url": "https://github.com/JerryI/wljs-plugin-example-2"
},
"dependencies": {
"@rollup/plugin-commonjs": "^25.0.4",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-terser": "^0.4.4",
"rollup": "^3.21.6"
}
}
This defines an action button placed in the top-bar menu. When clicked, it triggers the example-eval-all
event in the global event pool, which we will handle later.
Main Kernel Package
UI Element
To inject new UI elements, we use the CoffeeLiqueur`Notebook`AppExtensions`
interface:
BeginPackage["CoffeeLiqueur`Extensions`EvalAllButton`", {
"JerryI`Misc`Events`",
"JerryI`Misc`Events`Promise`",
"JerryI`WLX`Importer`",
"JerryI`WLX`WebUI`"
}]
Needs["CoffeeLiqueur`Notebook`AppExtensions`" -> "AppExtensions`"];
Begin["`Private`"]
rootFolder = $InputFileName // DirectoryName // ParentDirectory;
buttonTemplate = ImportComponent[FileNameJoin[{rootFolder, "templates", "Button.wlx"}]];
AppExtensions`TemplateInjection["AppNotebookTopBar"] = buttonTemplate[##, "HandlerFunction" -> processRequest]&;
End[]
EndPackage[]
The packages:
JerryI`Misc`Events`
JerryI`Misc`Events`Promise`
are used for event handling. The new UI element is injected via:
AppExtensions`TemplateInjection["AppNotebookTopBar"] = buttonTemplate[##, "HandlerFunction" -> processRequest]&;
Creating the Button
A more convenient way to create UI elements is through WLX. We include the necessary contexts:
JerryI`WLX`Importer`,
JerryI`WLX`WebUI`
Now, let's create our button:
Component[OptionsPattern[]] := With[{
messager = OptionValue["Messanger"],
globalControls = OptionValue["Controls"],
appEvents = OptionValue["AppEvent"],
modals = OptionValue["Modals"],
UId = CreateUUID[],
listener = CreateUUID[],
processRequest = OptionValue["HandlerFunction"]
},
EventHandler[listener, {
"Button" -> Function[Null,
processRequest[globalControls, modals, messager, Global`$Client (*`*)]
]
}];
EventHandler[EventClone[globalControls], {
"example-eval-all" -> Function[Null, processRequest[globalControls, modals, messager, Global`$Client (*`*)]]
}];
{
<button title="Evaluate all" id="{UId}" class="text-red-400 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-md w-6 h-6">
<svg class="w-5 h-5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M2.78 2L2 2.41v12l.78.42 9-6V8l-9-6zM3 13.48V3.35l7.6 5.07L3 13.48z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 14.683l8.78-5.853V8L6 2.147V3.35l7.6 5.07L6 13.48v1.203z"/>
</svg>
</button>,
<WebUIEventListener Type={"click"} Id={UId} Pattern={"Button"} Event={listener} />
}
]
Options[Component] = {"Controls"->"", "AppEvent"->"", "Modals"->"", "HandlerFunction" -> Print};
Component
After restarting the app, you should see a new icon in the top bar and a new menu item.
Processing cells
As it was mentioned, we want to evaluate all cells, when a user clicks on an icon. Let us go back to Frontend.wl
and add more packages to work with cells and notebooks
Needs["CoffeeLiqueur`Notebook`Cells`" -> "cell`"];
Needs["CoffeeLiqueur`Notebook`" -> "nb`"];
Next define processRequest
processRequest[globalControls_String, modals_String, messager_String, client_] := With[{
notebookOnline = findNotebook[globalControls]
},
...
To find a notebook available in the current window one can echo-locate it using a global controls event pool
findNotebook[messagesPort_] := EventFire[messagesPort, "NotebookQ", True] /. {{___, n_nb`NotebookObj, ___} :> n};
If at least one notebook component is attached to the global events network, it will respond to this pattern.
Then we can sort out input cells and synchronously one by another
processRequest[globalControls_String, modals_String, messager_String, client_] := With[{
notebookOnline = findNotebook[globalControls]
},
If[!MatchQ[notebookOnline, _nb`NotebookObj],
EvetFire[messager, "Warning", "No active notebooks"];
Return[];
];
Echo["Processing!"];
With[{
inputCells = Select[notebookOnline["Cells"], cell`InputCellQ]
},
(* If you don't want to handle Kernel requests and prepare the rest -> use Notebooks public API *)
runNext[inputCells, Function[cell, EventFire[globalControls, "NotebookCellEvaluate", cell] ] ];
]
]
runNext[l_List, f_] := With[{rest = Drop[l, 1]},
Then[f[l // First], Function[Null,
runNext[rest, f]
] ]
] /; Length[l] > 0
runNext[_List, f_] := Echo["Done!"];
We took a shortcut here. To avoid complexity with requesting kernels, showing dialogs we evaluate cells indirectly using Notebook API interface by firing pattern
EventFire[globalControls, "NotebookCellEvaluate", cell] _Promise
as a result an attached notebook (if any) on the events network returns Promise
, which we can handle using Then
and propagate to other cell and etc. Global controls event network interconnects modules and temporary exposes end-points of different services.
Full source code can be found in this repository