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.
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
EventObjectsystem - Works seamlessly with
EventHandler
Example
You can now use it as a standard input element:
EventHandler[MyCustomInput2["Click me"], Print]