DEV Community

Cover image for WFC in Perl: A Server-Driven UI Approach for Perl
Elanat Framework
Elanat Framework

Posted on

WFC in Perl: A Server-Driven UI Approach for Perl

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
Enter fullscreen mode Exit fullscreen mode

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.

WebForms Core

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!");
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The package provides the WebForms Core Perl classes required by the application.

For example, the application can load the WebForms implementation with:

use WebForms;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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="/">
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

The server then creates a WebForms object:

my $form = WebForms->new;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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!");
Enter fullscreen mode Exit fullscreen mode

Finally, the generated WebForms Core response is returned:

$c->render(text => $form->response());
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

How the Memory Game Works

The server initially creates the game board by generating pairs of emoji cards:

my @cards = shuffle(
    qw(
        🍎 🍎
        🍋 🍋
        🍊 🍊
        🍉 🍉
        🍇 🍇
        🥝 🥝
        🍓 🍓
        🍒 🍒
    )
);
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

The tile is then visually selected and disabled:

$form->insert_class('$', "selected");
$form->set_disabled('$', 1);
$form->set_text('$', Fetch::get_attribute('$', "data-card"));
Enter fullscreen mode Exit fullscreen mode

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")
);
Enter fullscreen mode Exit fullscreen mode

If the values are equal, the match counter is increased:

$form->increase("matches", 1);
$form->remove_save("first-card");
$form->set_text("result", "Good");
Enter fullscreen mode Exit fullscreen mode

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!");
Enter fullscreen mode Exit fullscreen mode

Handling a Failed Match

If the two cards do not match, the commands wait for 800 milliseconds:

$form->delay(800);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

The second card is restored using the current target:

$form->set_text('$', "?");
$form->set_class('$', "");
$form->set_disabled('$', 0);
Enter fullscreen mode Exit fullscreen mode

The result is then updated:

$form->set_text("result", "Try Again");
$form->set_text("moves", Fetch::get_text("moves"));
Enter fullscreen mode Exit fullscreen mode

Starting a New Game

The reload command index is associated with the reset element:

$form->start_index("reload");
$form->reload_page();
Enter fullscreen mode Exit fullscreen mode

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

Serven-Driven UI for Perl

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)