WLJS LogoWLJS Notebook

Custom input elements

This guide demonstrates how to create custom input components using pure JavaScript and WLX, providing interactive elements that integrate seamlessly with the Wolfram Language expressions.

Creating a Basic Custom Component

You can define custom components using JavaScript cells and defining a frontend symbol. Here's a simple button component that fires events when clicked:

.js core.MyCustomComponent = async (args, env) => { const label = await interpretate(args[0], env); const ev = await interpretate(args[1], env); const button = document.createElement('button'); button.innerText = label; button.style.backgroundColor = "lightblue"; button.addEventListener('click', () => { server.kernel.io.fire(ev, true); }); env.element.appendChild(button); }
core.MyCustomComponent = async (args, env) => {
const label = await interpretate(args[0], env);
const ev = await interpretate(args[1], env);

const button = document.createElement('button');
button.innerText = label;
button.style.backgroundColor = "lightblue";

button.addEventListener('click', () => {
  server.kernel.io.fire(ev, true);
});

env.element.appendChild(button);
}

Defining the Display Rules

To make the component work with Wolfram Language's display system, you need to define the output form, that will use our frontend symbol as a display function:

MyCustomComponent /: MakeBoxes[m_MyCustomComponent, StandardForm] := With[{}, ViewBox[m, m] ]

and for slides, markdown cells use need to define WLXForm as well. This form does not have its own ViewBox and uses frontend objects instead:

MyCustomComponent /: MakeBoxes[m_MyCustomComponent, WLXForm] := With[{ o = CreateFrontEndObject[m] }, MakeBoxes[o, WLXForm] ]

Example

Now you can use the custom component in your notebook:

MyCustomComponent["Click me pls", "event1"] EventHandler["event1", Print];

(*VB[*)(MyCustomComponent["Click me pls", "event1"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KWlMIJ4gkPCtdC4tLsnPdc7PLcjPS80rCeYBijrnZCZnK+SmKhTkFAezAQVSy4BShgB8pBLc"*)(*]VB*)

Integrating with Standard Inputs

For better integration with the standard input system, you can wrap your component in an EventObject. This approach makes your component behave like built-in input elements such as InputRange or InputButton:

ClearAll[MyCustomComponent] MyCustomInput[label_String] := With[{uid = CreateUUID[]}, EventObject[<|"Id"->uid, "View"->MyCustomComponent[label, uid]|>] ]

This pattern automatically generates a unique event ID and wraps your component in an EventObject, making it compatible with EventHandler.

You do not need to define the output form, if the symbol is a View field of EventObject

Example

Easy and natural:

EventHandler[MyCustomInput["Click me"], Print]

When clicked, the button will trigger the event handler and print to the output.

Using WLX

WLX provides a more intuitive way to write markup compared to pure JavaScript:

.wlx MyCustomComponent2[OptionsPattern[]] := With[{ Event = OptionValue["Event"], Label = OptionValue["Label"], Uid = CreateUUID[] }, <div> <button id="{Uid}" style="background: lightblue" class="p-1"> <Label/> </button> <script type="module"> document.getElementById('<Uid/>').addEventListener('click', () => { server.kernel.io.fire('<Event/>', true); }) </script> </div> ] Options[MyCustomComponent2] = {"Event" -> "", "Label" -> "Click me"};

In order to use it the output cells of Wolfram Language, you need to wrap it with HTMLView with WLX processor:

HTMLView%5BMyCustomComponent2%5B%22Event%22-%3E%22test%22%2C%20%22Label%22-%3E%22Hello%20World!%22%5D%2C%20Prolog-%3EHTMLView%60WLXProcessor%5B%5D%5D%0AEventHandler%5B%22test%22%2C%20Print%5D%3B

However, there is a caveat - UId for DOM element is generated per MyCustomComponent2 constructor call. This means, you can't simply have multiple copies of the same input element in the notebook (if created by accident). To account this problem, we can generate DOM Id dynamically using string templates. Let's fix it:

.wlx MyCustomComponent2[OptionsPattern[]] := With[{ Event = OptionValue["Event"], Label = OptionValue["Label"] }, <div> <button id="#uid" style="background: lightblue" class="p-1"> <Label/> </button> <script type="module"> document.getElementById('#uid').addEventListener('click', () => { server.kernel.io.fire('<Event/>', true); }) </script> </div> ] Options[MyCustomComponent2] = {"Event" -> "", "Label" -> "Click me"};

And provide a post-processor to HTMLView, that will generate unique ids:

HTMLView%5BMyCustomComponent2%5B%22Event%22-%3E%22test%22%2C%20%22Label%22-%3E%22Hello%20World!%22%5D%2C%20Prolog-%3E%7B%0A%20%20%20%20HTMLView%60TemplateProcessor%5B%3C%7C%22uid%22%20-%3E%20CreateUUID%5B%5D%7C%3E%5D%2C%0A%20%20%20%20HTMLView%60WLXProcessor%5B%5D%0A%7D%5D%0AEventHandler%5B%22test%22%2C%20Print%5D%3B

(*VB[*)(FrontEndRef["90a96bea-5cd8-4d8c-ab0c-4dd2c38ebd1b"])(*,*)(*"1:eJxTTMoPSmNkYGAoZgESHvk5KRCeEJBwK8rPK3HNS3GtSE0uLUlMykkNVgEKWxokWpolpSbqmianWOiapFgk6yYmGSQDWSlGycYWqUkphkkAlGkWuw=="*)(*]VB*)

Combining WLX with EventObject

You can combine the WLX approach with the EventObject pattern from earlier:

MyCustomInput2[label_String] := With[{id = CreateUUID[]}, EventObject[<|"Id"->id, "View"-> HTMLView[MyCustomComponent2["Event"->id, "Label"->label], Prolog->{ HTMLView`TemplateProcessor[<|"uid" -> CreateUUID[]|>], HTMLView`WLXProcessor[] }]|>] ]

This creates a reusable input component that:

  • Uses WLX for cleaner markup
  • Integrates with the EventObject system
  • Works seamlessly with EventHandler

Example

You can now use it as a standard input element:

EventHandler[MyCustomInput2["Click me"], Print]

On this page