Communication
Here we will highlight a few methods for communication with Javascript
See also WLJS Functions
Direct call
Let us define our dummy function
.js
core.MyFunction = async (args, env) => {
const data = await interpretate(args[0], env);
alert(data);
}
then the fastest and most direct way of calling it is using FrontSubmit
FrontSubmit[MyFunction["Hello World!"]]
or chaining it with some other WLJS function
FrontSubmit[MyFunction[ReadClipboard[]]]
Execution in a cell
So-called FrontEndExecutable gives a DOM element our function because of their StandardForm is defined like that. This is the easiest way
.js
core.MyFunction2 = async (args, env) => {
const data = await interpretate(args[0], env);
env.element.innerHTML = `<span style="color:red">${data}</span>`;
}
Now we can call it from our cell
CreateFrontEndObject[MyFunction2["Hello World!"]]
it behaves like a symbol.
You don't have to always use CreateFrontEndObject. It is possible to force Wolfram Kernel to apply it automatically on output using MakeBoxes or use ViewBox directly i.e.
MyFunction2 /: MakeBoxes[m_MyFunction2, StandardForm] := (
ViewBox[m, m]
)
This is basically how Graphics and others are implemented.
Data fetching
A back communication is also possible
.js
core.MyFunction3 = async (args, env) => {
return [1,2,3,4,5,6];
}
and to read it synchronously
FrontFetch[MyFunction3[]]
or asynchronously
Then[FrontFetchAsync[MyFunction3[]], Function[data, Print[data]]]
Emitting events
We can call any WL function using Events system
.js
const button = document.createElement('button');
button.innerText = "Press me";
button.addEventListener('click', () => {
server.kernel.emitt('eventUid', 'True');
});
return button;
Now we can capture it using
EventHandler["eventUid", Print]
Custom UI component
Using JS one can craft its own UI components. For example
.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.emitt(ev, 'True');
});
env.element.appendChild(button);
}
Now we can make boxes for it
MyCustomComponent /: MakeBoxes[m_MyCustomComponent, StandardForm] := With[{},
ViewBox[m, m]
]
In action
MyCustomComponent["Click me pls", "event1"]
EventHandler["event1", Print];
Integrating with standard inputs
It is much easier to work with EventObject
ClearAll[MyCustomComponent]
MyCustomInput[label_String] := With[{uid = CreateUUID[]},
EventObject[<|"Id"->uid, "View"->MyCustomComponent[label, uid]|>]
]
in action it behaves similar to InputRange and etc
EventHandler[MyCustomInput["Click me"], Print]
Using WLX
It is much easier to write markup in WLX compared to JS
.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.emitt('<Event/>', 'True');
})
</script>
</div>
]
Options[MyCustomComponent2] = {"Event" -> "", "Label" -> "Click me"};
In action
MyCustomComponent2["Event"->"test", "Label"->"Hello World!"] // WLXEmbed
EventHandler["test", Print];
or combining with a previous technic
MyCustomInput2[label_String] := With[{uid = CreateUUID[]},
EventObject[<|"Id"->uid, "View"-> HTMLView[MyCustomComponent2["Label"->label, "Event"->uid]]|>]
]
You can use it as a normal input element
EventHandler[MyCustomInput2["Click me"], Print]
Side effects
To call another function after an elements was drawn see HTMLView page. See below
Using WLX + 2 ways binding
It is also possible to combine your custom input element with traditional Dynamics. For instance you want to create multiple input fields, which are synchronised with each other. Therefore you need a WLJS Functions with update
methods defined. One can generate those on-fly for each input element within a WLX script
.wlx
CustomInput[sym_, OptionsPattern[]] := Module[{
Label = OptionValue["Label"],
Ev = OptionValue["Event"],
Pattern = OptionValue["Pattern"],
Handler
},
With[{Template =
<div class="mt-2 flex">
<div style="width: 7rem" class="flex shrink-0 items-center rounded-l-md bg-white px-3 text-base text-gray-500 outline outline-1 -outline-offset-1 outline-gray-300 sm:text-sm/6"><Label/></div>
<input type="number" step="1" class="-ml-px block w-full grow rounded-r-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6" placeholder="0.0"/>
<script type="module">
core['<Handler/>'] = async (args, env) => {
const input = env.element.getElementsByTagName('input')[0];
const data = await interpretate(args[0], env);
input.value = +data.toFixed(4);
env.local.input = input;
input.addEventListener('change', () => {
console.warn(input.value);
env.local.skip = true;
server.kernel.emitt('<Ev/>', input.value, '<Pattern/>');
});
}
core['<Handler/>'].update = async (args, env) => {
if (env.local.skip) {
env.local.skip = false;
return;
}
console.log('Update');
env.local.input.value = +(await interpretate(args[0], env)).toFixed(4);
}
core['<Handler/>'].destroy = () => {
delete core['<Handler/>'];
}
core['<Handler/>'].virtual = true;
</script>
</div>
},
HTMLView[Template, Epilog->(Handler[sym])]
]
]
Options[CustomInput] = {"Label"->"", "Event"->"", "Pattern"->"Default"};
what happens here:
- we define a template for our custom input field
- we define a support script with a generated
Handler
function, which reads and updates this input field - we pack in into HTMLView, which calls our
Handler
on a provided argument after this component has been mounted
Let's see it in action
Emit event from WLJS
One can fire an event also using frontend's function EventFire
, which is effectively acts like server.kernel.emitt
being called from the WLJS Interpreter (i.e. using FrontSubmit or other and wrapped in Offload)
Not implemented! PR
EventHandler["internalEvent", Print];
FrontSubmit[EventFire["internalEvent", ReadClipboard[]] // Offload]
It will send an expression to be executed on the frontend, that reads a clipboard and fires back an event with a fetched data.
Request evaluation
It is also possible from Javascript to request an evaluation of any symbol using server
object
.js
const doc = document.createElement('span');
const run = async () => {
const data = await server.kernel.ask('RandomReal[{-1,1}, 3]');
const result = await interpretate(data, {});
doc.innerText = result.join(', ');
}
run();
return doc;