-
Notifications
You must be signed in to change notification settings - Fork 134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add example using JS framework (e.g. React, Vue) #124
Comments
As far as I understand, there is no writing practice and a good coding style for webui yet. When using libraries like webui in real world projects, you need to take into account, that the mechanisms of interlanguage interactions provided by them in multilingual programming (polyglot programming) do not allow you to combine policies (what a program should do) with an interaction protocol (what it can do) without imposing any restrictions. There should be a diagram illustrating the capabilities of webui Taking, as a framework for the development of presentation components, for example, React, several approaches to the implementation of a scheme of such interaction are seen. When using any approach, the output is the same product, its choice will be largely determined by the level of knowledge in front-end development. 1. Server Side GenerationThe first option is backend generation, when all page (form) html+css+js code every time is generated and sent to the browser from the target language. In this case, the server part acts as a static site generator, similar to Jekyll, forming entire pages on the fly depending on the context and sending them to the browser via webui. React features on the form: Features of React components: Required level of knowledge: 2. Static Components GenerationThe second option is when the bootstrap code of the current form is sent from the programming language, and the form itself is a combination of html + css + js with named (id) elements that act as component mounting nodes. The lifecycle management of components (whether it is React jsx components, or just code generation of DOM nodes) is carried out on the side of the programming language, mounting, unmounting and updating based on user input results is the concern of the backend. This option is closest to the mvvm paradigm, where the browser acts only as a V (view), which fits perfectly into the restrictions imposed by webui and very close to the role of forms and components on them in conventional desktop applications. React features on the form: React component Capabilities: Required level of knowledge: 3. Dynamic Components GenerationThe third option, respectively, is dynamic components generation. The only difference from the second option is that the components have a state and behavior, they cease to be pure functions, and this option may also be useful, but here you need to be careful that the business logic does not get out of the backend and in order not to lose full control of the backend over the frontend because of its excessive complexity. The state/behaviour should, logically, concern only the appearance (V) of the component, the request for the data required to form the component render, transmitting user input to the backend, as well as the implementation of a two-way data binding mechanism and events through, for example, the publisher subscriber model or the event bus. React features on the form: React component Capabilities: Required level of knowledge: To illustrate what has been said, let's take the example offered by React and implement it, for example, in the first variant (SSG) with the elements of the second one (node replacement on update): unit productlist;
{$mode delphi}{$H+}
interface
uses
WebUI, SysUtils;
type
TProduct = record
category: string;
price: string;
stocked: Boolean;
name: string;
end;
TProductList = array of TProduct;
TFilterableProductTable = class
private
FProducts: TProductList;
FFilterText: string;
FInStockOnly: Boolean;
FWindow: size_t;
procedure InitializeWindow;
procedure BindEvents;
procedure ShowView;
procedure HandleFilterTextChange(e: PWebUI_event_t);
procedure HandleInStockOnlyChange(e: PWebUI_event_t);
function FilterProducts: TProductList;
procedure RenderProductTable(products: TProductList);
function GetProductCategoryRow(category: string): string;
function GetProductRow(product: TProduct): string;
public
constructor Create(products: TProductList);
procedure Run;
end;
implementation
var
FilterableTableInstance: TFilterableProductTable;
procedure FilterTextChangeWrapper(e: PWebUI_event_t);
begin
FilterableTableInstance.HandleFilterTextChange(e);
end;
procedure InStockOnlyChangeWrapper(e: PWebUI_event_t);
begin
FilterableTableInstance.HandleInStockOnlyChange(e);
end;
constructor TFilterableProductTable.Create(products: TProductList);
begin
FProducts := products;
FFilterText := '';
FInStockOnly := False;
InitializeWindow;
BindEvents;
end;
procedure TFilterableProductTable.InitializeWindow;
begin
FWindow := webui_new_window;
end;
procedure TFilterableProductTable.BindEvents;
begin
FilterableTableInstance := Self;
webui_bind(FWindow, 'FilterTextChange', @FilterTextChangeWrapper);
webui_bind(FWindow, 'InStockOnlyChange', @InStockOnlyChangeWrapper);
end;
procedure TFilterableProductTable.ShowView;
function IfThen(AValue: Boolean; const ATrue: string; const AFalse: string = ''): string;
begin
if AValue then
Result := ATrue
else
Result := AFalse;
end;
begin
webui_show(FWindow, PChar(
'<!DOCTYPE html><html><head></head><body><div>' +
'<input type="text" value="' + FFilterText + '" onkeyup="webui_fn(`FilterTextChange`, event.target.value); console.log(event.target.value)">' +
'<label><input type="checkbox" ' + IfThen(FInStockOnly, 'checked') + ' onclick="webui_fn(`InStockOnlyChange`, event.target.checked); console.log(event.target.checked)"> Only show products in stock</label>' +
'</div>' +
'<table>' +
'<thead><tr><th>Name</th><th>Price</th></tr></thead>' +
'<tbody id="productTableBody"></tbody>' +
'</table></body></html>'));
end;
procedure TFilterableProductTable.HandleFilterTextChange(e: PWebUI_event_t);
begin
FFilterText := webui_get_string(e);
RenderProductTable(FilterProducts);
end;
procedure TFilterableProductTable.HandleInStockOnlyChange(e: PWebUI_event_t);
begin
FInStockOnly := webui_get_bool(e);
RenderProductTable(FilterProducts);
end;
function TFilterableProductTable.FilterProducts: TProductList;
var
filteredList: TProductList;
product: TProduct;
begin
SetLength(filteredList, 0);
for product in FProducts do
begin
if (FFilterText = '') or (Pos(LowerCase(FFilterText), LowerCase(product.name)) > 0) then
begin
if not FInStockOnly or (FInStockOnly and product.stocked) then
begin
SetLength(filteredList, Length(filteredList) + 1);
filteredList[Length(filteredList) - 1] := product;
end;
end;
end;
Result := filteredList;
end;
procedure TFilterableProductTable.RenderProductTable(products: TProductList);
var
tableBody: string;
product: TProduct;
currentCategory: string;
begin
tableBody := '';
currentCategory := '';
for product in products do
begin
if product.category <> currentCategory then
begin
tableBody := tableBody + GetProductCategoryRow(product.category);
currentCategory := product.category;
end;
tableBody := tableBody + GetProductRow(product);
end;
tableBody := 'document.getElementById("productTableBody").innerHTML = `' + tableBody + '`';
webui_run(FWindow, PChar(tableBody));
end;
function TFilterableProductTable.GetProductCategoryRow(category: string): string;
begin
Result := '<tr><th colspan="2">' + category + '</th></tr>';
end;
function TFilterableProductTable.GetProductRow(product: TProduct): string;
var
name: string;
begin
if product.stocked then
name := product.name
else
name := '<span style="color: red;">' + product.name + '</span>';
Result := '<tr><td>' + name + '</td><td>' + product.price + '</td></tr>';
end;
procedure TFilterableProductTable.Run;
begin
ShowView;
webui_wait;
end;
end. Use case program: program reactexample;
{$mode objfpc}{$H+}
uses
productlist;
var
Products: TProductList;
Product: TProduct;
FilterableProductTable: TFilterableProductTable;
begin
SetLength(Products, 6);
Product.Category := 'Fruits';
Product.Price := '$1';
Product.Stocked := True;
Product.Name := 'Apple';
Products[0] := Product;
Product.Category := 'Fruits';
Product.Price := '$1';
Product.Stocked := True;
Product.Name := 'Dragonfruit';
Products[1] := Product;
Product.Category := 'Fruits';
Product.Price := '$2';
Product.Stocked := False;
Product.Name := 'Passionfruit';
Products[2] := Product;
Product.Category := 'Vegetables';
Product.Price := '$2';
Product.Stocked := True;
Product.Name := 'Spinach';
Products[3] := Product;
Product.Category := 'Vegetables';
Product.Price := '$4';
Product.Stocked := False;
Product.Name := 'Pumpkin';
Products[4] := Product;
Product.Category := 'Vegetables';
Product.Price := '$1';
Product.Stocked := True;
Product.Name := 'Peas';
Products[5] := Product;
FilterableProductTable := TFilterableProductTable.Create(Products);
FilterableProductTable.Run;
FilterableProductTable.Free;
end. I am a pascalist, so the example is in free pascal, but no one prevents transpiling this example to any other programming language. Due to limitations, the current version of free pascal compiler does not yet have anonymous functions, and there is no possibility of passing the procedure of object as a procedure, as required by webui, for binding, so I had to insert a crutch in the form of a state variable. I know that it is possible to make a singleton, but it will still be the same. That's all, in other programming languages this problem will most likely not be, and in free pascal it will soon also be solved with, for example, anonymous functions. Trunk already has it. This example does not reveal the use of webui specifically with react, but gives an approximate understanding of how things are, what options there are and where to move on. There is an example of third approach There is also a fourth approach, but it is the limiting state of the third, when all or almost all of the business logic is on the frontend, as shown, for example, in the final implementation of React example, which we have just implemented in free pascal via webui, but in the opposite style. Backend in this case, can act as just a data source (repository), taking into account the same restrictions and communication methods that are provided by webui. But this is already moving away from the original idea of webui, where the browser is just a representation (form) of a desktop application written in any programming language, but it has a place to be. This approach may include such a promising direction as code transpilation in js or webassembly. The meaning of the approach will be to write the event handler code in the target programming language, and when compiling the application, all the code related to the browser will be transpiled and transferred to the frontend. But this approach requires additional work on the formation of unified boiler plates for webui and their automated processing for each programming language. In any case, on the browser side, with the help of a huge javascript ecosystem, it is advisable to solve tasks that for some reason (lack of a library, limitations of the language/compiler/operating system, etc.) are expensive, difficult or impossible to implement in a programming language. A concrete example can be web scraping. What is the point of using such proven methods as Selenium (webdriver) or more modern analogues on the backend to receive and process data from Internet sources, if the full power of modern desktop browsers is in your hands with webui? And here the second question arises that the study of javascript and its ecosystem, its limitations and capabilities, is a primary task for a programmer writing a backend in any programming language and using the browser via webui, or similar library, for the frontend. This greatly expands the set, understanding and applicability of certain solutions for specific projects. And this is a synergistic property of polyglot programming, when the final solution uses a combination of the best features of languages. Everything we talked about mainly concerns the development of web application interfaces, and it may not be very clear to programmers who just want to continue making forms with buttons and checkboxes as they usually did for desktop applications. But even for such fastidious people there is a good solution that completely fits into the scheme that we discussed above. The only thing is, we will now call such applications not desktop, but Rich Web Applications or RIA. And for our purposes, instead of React and JSX components, we can use Qooxdoo JavaScript Framework and its wonderful widgets. The simplest example of use with webui can be viewed here
|
Thank you, @delphius, for this interesting article. Yes, as you said, there are three approaches, Server Side Generation, Static Components Generation, Dynamic Components Generation, And maybe a fourth approach where the backend acts mainly as a data source. I look forward to writing examples in one or all approaches when I have free time. Maybe this will require more APIs in the WebUI. Let's see. |
I will try adding an example soon... |
Examples can be added, but I think webui should not become an overly heavy library, is should maintain simplicity |
This needs to add a new example I guess, while webui will be untouched. |
vue and react are commonly used frameworks at present, I use webui to collect data locally, go calls js to present data, but with ui.run and ui.Script, it always does not work, there is little information about webui, please have some examples as soon as possible, thank you |
Did you added |
one typical use case could be:
the existing examples are great, but we need something a little more complex and "practical" |
ideally I'd love to just be able to point webUI at a folder containing a built copy of my vue app. serving the static assets within the same script doesn't seem to work. |
@binarytide Can you please open a new issue for this? and add details and logs as possible to quickly solve it. |
I created a simple app for the purpose of having an example. It uses Go-WebUI and creates its frontend with Bun and Svelte 5. Feature- and design-wise, it is an infant, but the focus is on providing a simple example that is hopefully easy to understand eventually. https://github.com/ttytm/gsysmo I'm planning to keep one branch simple, without complicating things, just adding some concise notes that might help to point out some concepts on how this stack works together for the app. The goal is to provide a simple way to what is needed for a fully bundled executable. Another branch is planned to add some real features to the application, which will be beyond an example |
great, just to digress, I'd like to know if there are any system tray libraries I can use. @ttytm |
A C99 lib I know of is: https://github.com/zserge/tray/tree/8dd1358b92562faf7c032cf5362fa97cbc7e13e9 One drawback is that the last commit was more than 6 years ago. MacOS arm, for example, is not fully supported. However, if it is not a very complex tray that you plan to have, writing it from scratch could work, the platform apis are more or less straight forward. |
also to digress, for webui with go in particular, it could be using a built-in go https library and websocket support(instead of civetweb which is C), so you can get cross platform webui all in one binary without using webview libraries like what Wails does. on the other hand, compiling civetweb is not that difficult either as what webui does |
It will be an all-in-one binary using webui with civetweb. The caveat for some languages will be embedding the UI files, not the websocket. When free moments open up I'll gonna add the embedding and also a deployment workflow to show how to achieve a single-binary output in a CI pipeline. |
There is a fork which is actively maintained: https://github.com/dmikushin/tray |
Oh nice. That's actually really good to know thanks for sharing! |
No description provided.
The text was updated successfully, but these errors were encountered: