DEV Community

Cover image for New Server-Driven UI in Dart - WebForms Core
Elanat Framework
Elanat Framework

Posted on

New Server-Driven UI in Dart - WebForms Core

What if you could build interactive web interfaces in Dart, without writing a separate JavaScript UI layer?

What if the server could define not only the initial HTML, but also the UI behavior that should happen when the user interacts with that HTML?

A new approach is now available for Dart.

WebForms Core (WFC) is now available for Dart.

WebForms Core is a server-side UI manipulation technology created by Elanat. It allows server-side code to generate commands for manipulating the browser DOM through the WebFormsJS runtime.

And now, Dart can act as a WebForms Commander.

A Different Approach to Web UI

A typical Dart web application can use Dart to build its server-side application and return HTML to the browser. Client-side JavaScript can then take responsibility for interactive behavior.

WebForms Core takes a different approach.

The server can define UI operations such as:

  • creating HTML elements
  • changing styles
  • changing attributes
  • changing text
  • responding to events
  • reading values from the DOM
  • executing conditional logic
  • controlling UI flow

The browser does not need to understand Dart.

Instead, the server generates WebForms Core commands, and WebFormsJS interprets and executes those commands in the browser.

The architecture is:

Dart
  ↓
WebForms
  ↓
Action Controls
  ↓
WebFormsJS
  ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

This is not a JavaScript framework.

It is a Commander–Executor architecture.

Dart is the Commander.

WebFormsJS is the Executor.

A First Dart Example

Let's start with something very simple.

Imagine a page containing a <select> element. The user selects a color, and the background and text color of the page change immediately.

Here is the Dart code:

import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';

import 'package:webforms/webforms.dart';

void main() async {
  var router = Router();

  router.get('/script/web-forms.js', (Request request) {
    final file = File('web/script/web-forms.js');
    if (!file.existsSync()) {
      return Response.notFound('web-forms.js not found');
    }
    return Response.ok(
      file.readAsStringSync(),
      headers: {'Content-Type': 'application/javascript; charset=utf-8'},
    );
  });

  router.get('/', (Request request) {
    final form = WebForms();

    form.addOptionTag("<select>", "White", "white");
    form.addOptionTag("-", "Light Green", "lightgreen");
    form.addOptionTag("-", "Light Blue", "lightblue");
    form.addOptionTag("-", "Red", "red");
    form.addOptionTag("-", "Blue", "blue");
    form.addOptionTag("-", "Green", "green");
    form.addOptionTag("-", "Yellow", "yellow");
    form.addOptionTag("-", "Orange", "orange");
    form.addOptionTag("-", "Purple", "purple");
    form.addOptionTag("-", "Pink", "pink");
    form.addOptionTag("-", "Brown", "brown");
    form.addOptionTag("-", "Black", "black");
    form.addOptionTag("-", "Gray", "gray");

    form.setCommentEvent("-", HtmlEvent.onChange, index: "set-color");

    form.startIndex("set-color");
    form.setBackgroundColor("<body>", Fetch.getValue("<select>"));
    form.isEqualTo("black", Fetch.getValue("<select>"));
        form.setTextColor("<body>", "white");
    form.else_();
        form.setTextColor("<body>", "black");

    return Response.ok(
      _htmlForm() + form.exportToHtmlComment(),
      headers: {'Content-Type': 'text/html; charset=utf-8'},
    );
  });

  var server = await io.serve(
    router,
    InternetAddress.loopbackIPv4,
    8080,
  );

  print('Server running on http://${server.address.host}:${server.port}');
}

String _htmlForm() {
  return '''
<!DOCTYPE html>
<html>
<head>
  <title>Using WebForms Core</title>
  <script type="module" src="/script/web-forms.js"></script>
</head>
<body>
    <h1>WebForms Core in Dart</h1>
    Change to change color
    <select></select>
</body>
</html>
  ''';
}
Enter fullscreen mode Exit fullscreen mode

Screenshot after running the Dart application

WebForms Core Technology in Dart

There is no JavaScript event handler in the Dart application.

There is no JavaScript DOM manipulation code.

There is no client-side framework.

The behavior is described through the WebForms class.

What Does the Server Actually Send?

The initial response contains the normal HTML page, followed by a WebForms Core command block:

<!--[web-forms]
ao<select>=whiteWhite
ao-=lightgreenLight Green
ao-=lightblueLight Blue
ao-=redRed
ao-=blueBlue
ao-=greenGreen
ao-=yellowYellow
ao-=orangeOrange
ao-=purplePurple
ao-=pinkPink
ao-=brownBrown
ao-=blackBlack
ao-=grayGray
Eb-=onchangeset-color
#=set-color
bc<body>=@$v<select>
{et=black@$v<select>
tc<body>=white
}e
tc<body>=black
-->
Enter fullscreen mode Exit fullscreen mode

This is not JSON.

It is not an HTML attribute.

It is not a JavaScript program.

It is a WebForms Core Action Control command stream.

WebFormsJS detects the [web-forms] comment and processes it.

Then Something Interesting Happens

The browser receives the page.

WebFormsJS processes the commands.

The <select> element is populated.

The change event is connected.

The conditional behavior is prepared.

And the server-generated behavior becomes normal browser behavior.

The resulting DOM contains:

<select onchange="CommentBack(event, 'set-color', '')">
    <option value="white">White</option>
    <option value="lightgreen">Light Green</option>
    <option value="lightblue">Light Blue</option>
    ...
    <option value="black">Black</option>
    <option value="gray">Gray</option>
</select>
Enter fullscreen mode Exit fullscreen mode

The important part is the execution model.

After the initial response, the interaction does not require another server request.

The browser executes the generated behavior locally.

In this example, the sequence is essentially:

Server
  ↓
WebForms Commands
  ↓
Initial HTML Response
  ↓
WebFormsJS
  ↓
DOM + Event
  ↓
Browser Execution
Enter fullscreen mode Exit fullscreen mode

This is one of the important ideas behind WebForms Core.

The Server Defines the Behavior

Look at this part again:

form.startIndex("set-color");
form.setBackgroundColor("<body>", Fetch.getValue("<select>"));

form.isEqualTo("black", Fetch.getValue("<select>"));
    form.setTextColor("<body>", "white");
form.else_();
    form.setTextColor("<body>", "black");
Enter fullscreen mode Exit fullscreen mode

The Dart server code describes the behavior.

It says:

  1. When the set-color event occurs, get the value of the <select>.
  2. Set the body background color to that value.
  3. Check whether the selected value is black.
  4. If it is black, make the text white.
  5. Otherwise, make the text black.

The logic is authored on the server.

The execution happens in the browser.

That distinction is fundamental.

Server-Orchestrated UI

Dart Programming with WebForms Core Technology

WebForms Core can be described as Server-Orchestrated UI.

The server does not continuously mirror the browser DOM.

It does not need a virtual DOM.

It does not require a separate frontend project.

Instead, the server sends executable UI commands, while WebFormsJS provides the browser-side runtime that executes them.

This creates a clear separation:

Server
Commander
    ↓
Commands
    ↓
Browser
Executor
    ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

The WebForms class is responsible for constructing the commands.

WebFormsJS is responsible for parsing and executing them.

But What About Server Requests?

WebForms Core does not require every interaction to be sent back to the server.

That is an important distinction.

There are two possible patterns.

Client execution

The server can send behavior that WebFormsJS executes directly in the browser.

For example:

User changes select
        ↓
WebFormsJS
        ↓
Read DOM value
        ↓
Change DOM
Enter fullscreen mode Exit fullscreen mode

No additional server request is necessary.

Server execution

Other interactions can send data back to the server.

For example:

User submits form
        ↓
      Server
        ↓
   Dart WebForms
        ↓
 WebForms Commands
        ↓
     Browser
Enter fullscreen mode Exit fullscreen mode

The server can then generate a new response containing only the UI operations required for that interaction.

This makes WebForms Core a hybrid system.

Some behavior can execute entirely in the browser.

Other behavior can involve the server.

A More Traditional Dart Example

Now consider a form.

The user enters a name, chooses a font size, enters a background color, and submits the form.

The Dart application can process the request:

var body = await request.readAsString();
var formData = Uri.splitQueryString(body);

if (formData['btn_SetBodyValue'] != null) {
  var name = formData['txt_Name'] ?? '';
  var backgroundColor = formData['txt_BackgroundColor'] ?? '';
  var fontSize = int.tryParse(formData['txt_FontSize'] ?? '16') ?? 16;

  final form = WebForms();

  form.setFontSize(InputPlace.tag('form'), '${fontSize}px');
  form.setBackgroundColor(InputPlace.tag('form'), backgroundColor);
  form.setDisabled(InputPlace.name('btn_SetBodyValue'), true);

  form.addTag(InputPlace.tag('form'), 'h3');
  form.setText(InputPlace.tag('h3'), 'Welcome $name!');

  return Response.ok(
    form.response(),
    headers: {
      'Content-Type': 'text/plain; charset=utf-8',
    },
  );
}
Enter fullscreen mode Exit fullscreen mode

Notice what the server returns.

It does not have to return the entire HTML document.

It can return the commands required to update the existing page.

The server might effectively say:

Change the form font size.
Change the form background.
Disable the submit button.
Add an h3 element.
Set its text.
Enter fullscreen mode Exit fullscreen mode

WebFormsJS executes those commands against the existing DOM.

Dart Without a Separate UI JavaScript Layer

This is where the Dart implementation becomes interesting.

The developer can write:

final form = WebForms();

form.setBackgroundColor(InputPlace.tag('form'), backgroundColor);
form.setDisabled(InputPlace.name('btn_SetBodyValue'), true);
form.addTag(InputPlace.tag('form'), 'h3');
form.setText(InputPlace.tag('h3'), 'Welcome $name!');
Enter fullscreen mode Exit fullscreen mode

Instead of writing a separate JavaScript implementation for each of these operations, the Dart application constructs the WebForms Core command stream.

The browser runtime handles the execution.

WebForms Core Is More Than HTML Generation

It would be easy to look at this and think:

"This is just server-side HTML generation."

It is not.

HTML generation is only one part of the system.

The WebForms class can construct operations involving:

  • DOM manipulation
  • attributes
  • styles
  • events
  • conditions
  • DOM value retrieval
  • UI state
  • element creation
  • element replacement
  • workflow
  • client-side execution
  • server interaction

The important unit is not simply HTML.

It is the command.

The Commander–Executor Model

WebForms Core uses two major sides.

Commander

The WebForms class runs on the server.

In this case:

Dart
  ↓
WebForms
Enter fullscreen mode Exit fullscreen mode

The Commander constructs the UI commands.

Executor

WebFormsJS runs in the browser.

It contains the runtime required to interpret and execute those commands.

WebFormsJS
  ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

Together:

Dart WebForms Commander
          ↓
    Action Controls
          ↓
      WebFormsJS
          ↓
       HTML DOM
Enter fullscreen mode Exit fullscreen mode

This architecture allows different server-side languages to use the same browser execution model.

Dart is now another Commander.

Why Dart?

Dart is already used across server-side and application development, and the Shelf ecosystem provides a lightweight foundation for HTTP applications.

WebForms Core introduces another possibility for Dart web development:

Dart server
     +
WebForms Commander
     +
WebFormsJS
     =
Server-Orchestrated Web UI
Enter fullscreen mode Exit fullscreen mode

Instead of introducing a large frontend framework for every interactive page, developers can keep more of the UI behavior in their server-side Dart code.

That does not mean every application should work this way.

It means there is another architectural option.

This Is Only the Beginning

The examples above are deliberately small.

The interesting question is what happens when the same model is applied to more complex interactions.

What if the server can construct:

  • multi-step UI workflows
  • conditional DOM operations
  • dynamic forms
  • event-driven interactions
  • partial UI updates
  • client-side state operations
  • complex DOM workflows
  • browser-side execution without additional requests

Then the WebForms class becomes more than a response builder.

It becomes a UI Commander.

And WebFormsJS becomes more than a JavaScript helper.

It becomes the Browser Executor.

WebForms Core for Dart

WebForms Core for Dart is now available as the webforms Dart package.

The package provides the WebForms Commander:

import 'package:webforms/webforms.dart';
Enter fullscreen mode Exit fullscreen mode

Install it with:

dart pub add webforms
Enter fullscreen mode Exit fullscreen mode

The architecture remains the same:

Dart
  ↓
WebForms Commander
  ↓
WebForms Core Commands
  ↓
WebFormsJS Runtime
  ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

The goal is simple:

Write the UI behavior in Dart. Let the browser execute it.

This brings WebForms Core's server-orchestrated UI architecture to Dart, allowing Dart applications to generate WebForms Core commands and control browser-side UI behavior through the WebFormsJS Runtime.

WebForms Core is now available for Dart.

Related links

In Elanat:

In GitHub:

In pub.dev:

Top comments (0)