Perl has long been used for server-side web development, but modern web applications often rely heavily on JavaScript frameworks to manipulate the browser interface. WebForms Core (WFC) provides another approach: the server can generate UI commands that are executed by a browser runtime. This article demonstrates how WebForms Core can be used with Perl and the Mojolicious framework to build an interactive web interface while keeping the UI logic on the server.
What is WebForms Core?
WebForms Core is a server-side UI manipulation technology created by Elanat that allows a server application to generate commands for manipulating the HTML DOM and controlling UI behavior in the browser. Instead of requiring a separate frontend application or a JavaScript framework for every interaction, the server uses the WebForms class to construct WebForms Core commands, and the WebFormsJS browser runtime receives and executes those commands. The architecture separates the server-side Commander from the browser-side Executor: the server generates UI commands, while WebFormsJS parses and executes them against the DOM. WebForms Core is not limited to forms or CRUD operations and can be used for general UI manipulation, automation, and interactive web applications.
WebForms Core commands are generated by methods of the WebForms class, so developers do not need to construct the command syntax manually. Elanat refers to these commands as "Action Controls".
WebForms Core in Perl
The Perl implementation of WebForms Core provides the WebForms class for generating WebForms Core commands from a Perl application.
The architecture can be summarized as:
Perl Application
|
v
WebForms Class
|
| WebForms Core Commands
|
| HTTP Response
|
v
WebFormsJS
|
v
HTML DOM
The important point is that the Perl application does not need to directly manipulate the browser DOM. Instead, the server creates commands describing the required UI operations.
For example, the server can instruct the browser to change a font size, change a background color, disable a button, add an element, and set its text.
The WebForms Class
The WebForms class is the server-side Commander of WebForms Core.
In Perl, an application creates a WebForms object and uses its methods to construct UI commands:
my $form = WebForms->new;
$form->set_font_size(InputPlace::tag('form'), "${fontSize}px");
$form->set_background_color(InputPlace::tag('form'), $backgroundColor);
$form->set_disabled(InputPlace::name('btn_SetBodyValue'), 1);
$form->add_tag(InputPlace::tag('form'), 'h3');
$form->set_text(InputPlace::tag('h3'), "Welcome $name!");
The InputPlace helpers specify which HTML elements should be targeted. For example, InputPlace::tag('form') selects the form element, while InputPlace::name('btn_SetBodyValue') selects an element by its name attribute.
The commands are then returned as part of the HTTP response:
$c->render(text => $form->response());
This makes the WebForms class a server-side UI command generator. The Perl application determines what should happen to the interface, while the browser runtime performs the actual DOM operations.
WebFormsJS
WebFormsJS is the browser-side runtime of WebForms Core.
It is included in the HTML page as a JavaScript module or script and is responsible for receiving, parsing, and executing WebForms Core commands in the browser.
For example:
<script type="module" src="/script/web-forms.js"></script>
WebFormsJS acts as the Executor between the server-generated commands and the browser DOM.
The complete execution model is therefore:
Server
|
| WebForms commands
|
v
HTTP Response
|
v
WebFormsJS
|
| DOM operations
|
v
Browser
This means that the Perl application does not need to generate custom JavaScript for every UI operation. The server can use the WebForms class to express the required UI changes, while WebFormsJS handles their execution.
Installing WFC from CPAN
The Perl implementation of WebForms Core is distributed as the WFC package.
After installing Perl and cpanm, the package can be installed with:
cpanm WFC
The package provides the WebForms Core Perl classes required by the application.
For example, the application can load the WebForms implementation with:
use WebForms;
The CPAN distribution is useful when WebForms Core is installed as a dependency of a Perl web application rather than copied directly into the project.
Using WebForms Core with Mojolicious
Mojolicious is a Perl web framework that can be used to build the server application. WebForms Core can be integrated into a Mojolicious application by creating a WebForms object inside a route and returning its generated response.
The following example demonstrates a complete interaction.
The user enters a name, font size, and background color. When the form is submitted, Perl reads these values, creates WebForms Core commands, and returns them to the browser.
use Mojolicious::Lite;
use WebForms;
post '/' => sub {
my $c = shift;
my $name = $c->param('txt_Name');
my $backgroundColor = $c->param('txt_BackgroundColor');
my $fontSize = $c->param('txt_FontSize');
my $form = WebForms->new;
$form->set_font_size(InputPlace::tag('form'), "${fontSize}px");
$form->set_background_color(InputPlace::tag('form'), $backgroundColor);
$form->set_disabled(InputPlace::name('btn_SetBodyValue'), 1);
$form->add_tag(InputPlace::tag('form'), 'h3');
$form->set_text(InputPlace::tag('h3'), "Welcome $name!");
$c->render(text => $form->response());
};
get '/' => sub {
my $c = shift;
$c->render(text => <<'HTML');
<!DOCTYPE html>
<html>
<head>
<title>Using WebForms Core in Perl</title>
<script type="module" src="/script/web-forms.js"></script>
</head>
<body>
<form method="post" action="/">
<label for="txt_Name">Your Name</label>
<input name="txt_Name" id="txt_Name" type="text" />
<br>
<label for="txt_FontSize">Set Font Size</label>
<input name="txt_FontSize" id="txt_FontSize" type="number" value="16" min="10" max="36" />
<br>
<label for="txt_BackgroundColor">Set Background Color</label>
<input name="txt_BackgroundColor" id="txt_BackgroundColor" type="text" />
<br>
<input name="btn_SetBodyValue" type="submit" value="Click to send data" />
</form>
</body>
</html>
HTML
};
app->start;
How the Example Works
The initial GET / request returns the HTML page. The page contains a normal HTML form and loads web-forms.js.
No WebForms Core-specific attributes are required in the form:
<form method="post" action="/">
When the user submits the form, the POST / route receives the submitted values:
my $name = $c->param('txt_Name');
my $backgroundColor = $c->param('txt_BackgroundColor');
my $fontSize = $c->param('txt_FontSize');
The server then creates a WebForms object:
my $form = WebForms->new;
The UI operations are defined on the server:
$form->set_font_size(InputPlace::tag('form'), "${fontSize}px");
$form->set_background_color(InputPlace::tag('form'), $backgroundColor);
$form->set_disabled(InputPlace::name('btn_SetBodyValue'), 1);
The server also creates an h3 element and sets its text:
$form->add_tag(InputPlace::tag('form'), 'h3');
$form->set_text(InputPlace::tag('h3'), "Welcome $name!");
Finally, the generated WebForms Core response is returned:
$c->render(text => $form->response());
WebFormsJS receives this response in the browser and executes the generated commands. The result is that the form's font size and background color change, the submit button becomes disabled, and a new heading containing the user's name is added to the page.
Server-Driven UI
This example demonstrates the basic Server-Driven UI model provided by WebForms Core.
The browser does not need a separate frontend application to implement the UI logic described by WebForms Core commands. The server determines the UI operations:
User Input
|
v
Mojolicious
|
v
WebForms
|
| UI Commands
|
v
HTTP Response
|
v
WebFormsJS
|
v
DOM
This is different from the traditional approach where the server primarily returns HTML while JavaScript code in the frontend contains the logic for manipulating that HTML.
With WebForms Core, the server can orchestrate UI operations through commands while WebFormsJS provides the browser-side runtime that executes those commands.
Building a Memory Game with WebForms Core
WebForms Core is not limited to traditional forms or CRUD interfaces. The following example demonstrates a small memory card game implemented with Perl, Mojolicious, and WebForms Core.
The game contains eight pairs of emoji cards, for a total of 16 tiles. The cards are shuffled on the server when a new game starts. The browser then uses WebForms Core commands to reveal cards, compare pairs, count moves and matches, and reset the game.
This example is particularly useful for demonstrating that WebForms Core can handle interactive browser behavior without requiring a separate frontend application.
Memory Tiles Example
use Mojolicious::Lite;
use List::Util qw(shuffle);
use WebForms;
get '/' => sub {
my $c = shift;
my @cards = shuffle(
qw(
🍎 🍎
🍋 🍋
🍊 🍊
🍉 🍉
🍇 🍇
🥝 🥝
🍓 🍓
🍒 🍒
)
);
my $html = '';
for my $i (0 .. $#cards) {
$html .= qq{<button id="tag-$i" class="tile" data-card="$cards[$i]">?</button>\n};
}
my $form = WebForms->new;
$form->set_comment_event("<button>*", HtmlEvent::OnClick, "play");
$form->set_comment_event("reset", HtmlEvent::OnClick, "reload");
$form->remove_all_save();
$form->start_index("play");
# First tile
$form->not_exist(Fetch::save("first-card"));
$form->start_bracket();
$form->save_id('$', "first-id");
$form->save_attribute('$', "data-card", "first-card");
$form->insert_class('$', "selected");
$form->set_disabled('$', 1);
$form->set_text('$', Fetch::get_attribute('$', "data-card"));
$form->wf_break();
$form->end_bracket();
$form->increase("moves", 1);
# Second tile
$form->set_class('$', "selected");
$form->set_disabled('$', 1);
$form->set_text('$', Fetch::get_attribute('$', "data-card"));
# Match
$form->is_equal_to(
Fetch::save("first-card"),
Fetch::get_attribute('$', "data-card")
);
$form->start_bracket();
$form->increase("matches", 1);
$form->remove_save("first-card");
$form->set_text("result", "Good");
$form->is_equal_to("8", Fetch::get_text("matches"));
$form->message("🎉 You Win!");
$form->wf_break();
$form->end_bracket();
# No match
$form->delay(800);
# Return first card
$form->set_text(Fetch::save("first-id"), "?");
$form->set_class(Fetch::save("first-id"), "");
$form->set_disabled(Fetch::save("first-id"), 0);
$form->remove_save("first-card");
# Return second card
$form->set_text('$', "?");
$form->set_class('$', "");
$form->set_disabled('$', 0);
$form->set_text("result", "Try Again");
$form->set_text("moves", Fetch::get_text("moves"));
$form->start_index("reload");
$form->reload_page();
$c->render(text => $form->export_to_html_comment() . <<HTML);
<!DOCTYPE html>
<html>
<head>
<title>Memory Tiles in Perl</title>
<script type="module" src="/script/web-forms.js"></script>
<style>
body {
font-family: sans-serif;
text-align: center;
}
#board {
display: grid;
grid-template-columns: repeat(4, 80px);
gap: 10px;
justify-content: center;
margin: 30px auto;
}
.tile {
width: 80px;
height: 80px;
font-size: 32px;
cursor: pointer;
}
#reset {
cursor: pointer;
}
</style>
</head>
<body>
<h1>Memory Tiles</h1>
<div id="board">
$html
</div>
<p>Moves: <span id="moves">0</span></p>
<p id="result"></p>
<p>Matches: <span id="matches">0</span></p>
<b id="reset">New Game</b>
</body>
</html>
HTML
};
app->start;
How the Memory Game Works
The server initially creates the game board by generating pairs of emoji cards:
my @cards = shuffle(
qw(
🍎 🍎
🍋 🍋
🍊 🍊
🍉 🍉
🍇 🍇
🥝 🥝
🍓 🍓
🍒 🍒
)
);
The shuffle function randomizes the cards for each new game. Perl then generates the HTML buttons for the board.
The important part of the example is that the game behavior is defined through the WebForms class.
Registering Events
The following commands associate browser events with WebForms Core command indexes:
$form->set_comment_event("<button>*", HtmlEvent::OnClick, "play");
$form->set_comment_event("reset", HtmlEvent::OnClick, "reload");
Every tile button is associated with the play command sequence, while the reset element is associated with the reload sequence.
The generated commands are placed into the HTML response using:
$form->export_to_html_comment()
This allows WebFormsJS to discover the server-generated commands from the HTML response.
Selecting the First Card
The game uses WebForms Core Save values to remember information about the first selected card:
$form->not_exist(Fetch::save("first-card"));
When no first card exists, the clicked tile becomes the first selected card.
The example stores both the card identifier and its data-card value:
$form->save_id('$', "first-id");
$form->save_attribute('$', "data-card", "first-card");
The tile is then visually selected and disabled:
$form->insert_class('$', "selected");
$form->set_disabled('$', 1);
$form->set_text('$', Fetch::get_attribute('$', "data-card"));
The $ InputPlace refers to the current target element.
Comparing the Cards
When the second tile is selected, the game compares its data-card value with the saved value from the first tile:
$form->is_equal_to(
Fetch::save("first-card"),
Fetch::get_attribute('$', "data-card")
);
If the values are equal, the match counter is increased:
$form->increase("matches", 1);
$form->remove_save("first-card");
$form->set_text("result", "Good");
When all eight pairs have been matched, the game displays a winning message:
$form->is_equal_to("8", Fetch::get_text("matches"));
$form->message("🎉 You Win!");
Handling a Failed Match
If the two cards do not match, the commands wait for 800 milliseconds:
$form->delay(800);
The first card is then restored using the saved element identifier:
$form->set_text(Fetch::save("first-id"), "?");
$form->set_class(Fetch::save("first-id"), "");
$form->set_disabled(Fetch::save("first-id"), 0);
The second card is restored using the current target:
$form->set_text('$', "?");
$form->set_class('$', "");
$form->set_disabled('$', 0);
The result is then updated:
$form->set_text("result", "Try Again");
$form->set_text("moves", Fetch::get_text("moves"));
Starting a New Game
The reload command index is associated with the reset element:
$form->start_index("reload");
$form->reload_page();
When the player selects New Game, WebFormsJS executes the generated reload command and the server creates a new randomized board.
Screenshot of Memory Tiles Game
What This Example Demonstrates
The memory game demonstrates several important WebForms Core capabilities in a single Perl application:
- Event registration with
set_comment_event - Command indexes with
start_index - Conditional execution with
is_equal_to - Temporary browser-side values with
Save - DOM targeting with
InputPlace - Attribute and text retrieval with
Fetch - DOM modification
- Class manipulation
- Element disabling
- Counters and state changes
- Delayed execution
- Browser messages
- Page reload
- Server-generated commands embedded in HTML comments
Most importantly, the example demonstrates that WebForms Core can be used for interactive browser applications, not only traditional server forms.
The server defines the game behavior and generates the WebForms Core command sequence. WebFormsJS then executes that sequence in the browser.
This provides a different model from a conventional JavaScript application: instead of implementing the complete game logic in a frontend JavaScript application, the server can construct the interaction flow through WebForms Core commands.
Why Use WebForms Core with Perl?
WebForms Core gives Perl developers another option for building interactive web interfaces.
A Perl application can keep its application and UI orchestration logic on the server while using a browser runtime for command execution. The developer does not need to introduce a separate React, Vue, or similar frontend project simply to perform server-controlled UI operations.
This approach is particularly interesting for Perl applications that already have server-side business logic and want to add richer browser interaction without moving the entire application UI layer into a JavaScript framework.
WebForms Core also keeps the HTML document model visible. The application works with HTML elements and selectors rather than replacing the HTML document with a separate component model.
Conclusion
WebForms Core provides a Server-Driven UI approach for Perl by introducing a server-side WebForms class and a browser-side WebFormsJS runtime.
The Perl application acts as the Commander: it determines which UI operations should occur and generates the corresponding commands. WebFormsJS acts as the Executor: it receives those commands and performs the required operations on the browser DOM.
With the WFC package available through CPAN, this architecture can be used directly from Perl applications such as Mojolicious. The result is a model in which Perl remains responsible for application and UI orchestration logic, while the browser executes the commands through WebFormsJS.
Related links
In Elanat:
in GitHub:
in CPAN:


Top comments (0)