Guidelines
Store state only on server
It is very hesitating to use pure JS in order to keep the slider's state or switches in the browser, even some logic of your application. However, it leads to some complicated problems to be solved such as data or state synchronization, management of shared states and etc for which senior React developers got paid. If everything you create does not escape the borders of your [[component]] and Wolfram Language it will make your life easier.
The presented framework was designed mostly for a desktop or cloud-based applications with 1-3 users (browsers tabs don't count) and a single instance of Wolfram Kernel running. Storing everything on a server comes with almost zero cost of resources.
Even if you have a single button, use WebUIEventListener
and handle its action on a server.
Use Javascript for DOM manipulations only
or for decorative purposes
Every action has to be approved by the server's logic. Thinking about it if you were writing a regular desktop application (using QT for example). WebSockets is no longer a bottleneck for a simple UI actions, such as typing or clicking a button.
Enforce server side-rendering
This is done automatically, since there is no client-side rendering (or hydrator like in React or Angular) tool provided. The general idea is to send a ready-to-go HTML tree including supporting Javascript code via WebSockets, similar to setInnerHTML
method used on a web. See WebUILazyLoad
, WebUIContainer
, WebUIRefresh
and etc.
Preparations
The following libraries are needed for proper functioning of WebUI framework
WebUI is a dynamic framework, which demands a live WebSocket connection. Here is a server script with comments, that provides a minimal set of libraries for a working server
#!/usr/bin/env wolframscript
(* ::Package:: *)
SetDirectory[If[StringQ[NotebookDirectory[] ], NotebookDirectory[], DirectoryName[$InputFileName] ] ]
Uncompress["1:eJx1T8tuwjAQDBT1wo0/6Ac0vveKKAoNCJoDZ5NugsXGtrwbBf6edawKLlzG+5rxzMfJ/TZvWZbRVKBomkms5wLfBmF1NcR0qN6lL/fbfMC0j4Q18
PM80mkmcDR8TlexK0Ug7SKnAh5f9F0aRoGi8y5wVUp5Zvb0pVTQQ96KTn/qCULtLIPlvHad2kAIt0IN+Imu1uh1fdEtkOq0seoHggVUydForuJgbJsCLgSWTj7r7d/q6gMQGWfT
Lt7KLPp4ZPq3+qz0Iv6Yddcj3gGoDVPG"];
PacletRepositories[{
Github -> "https://github.com/KirillBelovTest/Objects",
Github -> "https://github.com/JerryI/Internal",
Github -> "https://github.com/JerryI/CSocketListener" -> "dev2024",
Github -> "https://github.com/JerryI/TCPServer",
Github -> "https://github.com/JerryI/HTTPHandler",
Github -> "https://github.com/JerryI/WebSocketHandler",
Github -> "https://github.com/JerryI/wl-misc" -> "dev",
Github -> "https://github.com/JerryI/wl-wljs-packages",
Github -> "https://github.com/JerryI/wl-wlx"
}]
RandomWord[];
(* Here you can configure *)
ENV = <|
"WSPort" -> 8011,
"HTTPPort" -> 8010,
"Host" -> "127.0.0.1"
|>
ENV["HAddr"] := StringTemplate["``:``"][ENV["Host"], ENV["HTTPPort"]]
ENV["WAddr"] := StringTemplate["``:``"][ENV["Host"], ENV["WSPort"]]
(* TCP Server *)
<<KirillBelov`Objects`
<<KirillBelov`Internal`
<<KirillBelov`CSockets`
<<KirillBelov`TCPServer`
(* HTTP services *)
<<KirillBelov`HTTPHandler`
<<KirillBelov`HTTPHandler`Extensions`
(* WS services *)
<<KirillBelov`WebSocketHandler`
(* WLX services *)
<<JerryI`WLX`
<<JerryI`WLX`Importer`
<<JerryI`WLX`WLJS`
<<JerryI`WLX`WebUI`
(* Events system and WLJS communication *)
<<JerryI`Misc`Events`
<<KirillBelov`CSockets`EventsExtension`
<<JerryI`Misc`WLJS`Transport`
<<JerryI`Misc`Async`
<<JerryI`WLJSPM`
WLJS`PM`Repositories[{
Github -> "https://github.com/JerryI/wljs-interpreter" -> "dev"
}]
(* import libs data *)
Map[Function[path,
Block[{System`$RemotePackageDirectory = FileNameJoin[{"wljs_packages", FileNameSplit[path] // First}]},
FileNameJoin[{"wljs_packages", path}] // Get // Quiet;
];
], WLJS`PM`Includes["kernel"] ];
Print["Staring HTTP server..."];
tcp = TCPServer[];
tcp["CompleteHandler", "HTTP"] = HTTPPacketQ -> HTTPPacketLength;
tcp["MessageHandler", "HTTP"] = HTTPPacketQ -> http;
(* Entry Point for all requests *)
App = ImportComponent["App.wlx"];
http = HTTPHandler[];
http["MessageHandler", "Index"] = AssocMatchQ[<|"Method" -> "GET"|>] -> App
SocketListen[CSocketOpen[ENV["HAddr"]], tcp@#&]
Print["Staring WS/HTTP server..."];
wcp = TCPServer[]
wcp["CompleteHandler", "WebSocket"] = WebSocketPacketQ -> WebSocketPacketLength
wcp["MessageHandler", "WebSocket"] = WebSocketPacketQ -> ws
(* configure the handler for WLJS communications *)
ws = WebSocketHandler[]
ws["MessageHandler", "Evaluate"] = Function[True] -> WLJSTransportHandler
SocketListen[CSocketOpen[ENV["WAddr"]], wcp@#&, "SocketEventsHandler"->CSocketsClosingHandler]
StringTemplate["open http://``"][ENV["HAddr"]] // Print;
While[True, Pause[1]];